mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
add raw services support
This commit is contained in:
parent
7300002226
commit
3b3dbb4f40
@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
### Added
|
### 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()`
|
* 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
|
### Changed
|
||||||
|
|
||||||
* `.to_async()` handler can return `Responder` type #792
|
* `.to_async()` handler can return `Responder` type #792
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* Fix async web::Data factory handling
|
* Fix async web::Data factory handling
|
||||||
|
@ -136,7 +136,9 @@ pub mod dev {
|
|||||||
pub use crate::config::{AppConfig, AppService};
|
pub use crate::config::{AppConfig, AppService};
|
||||||
pub use crate::info::ConnectionInfo;
|
pub use crate::info::ConnectionInfo;
|
||||||
pub use crate::rmap::ResourceMap;
|
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::form::UrlEncoded;
|
||||||
pub use crate::types::json::JsonBody;
|
pub use crate::types::json::JsonBody;
|
||||||
pub use crate::types::readlines::Readlines;
|
pub use crate::types::readlines::Readlines;
|
||||||
|
@ -308,12 +308,13 @@ struct CookieValue {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct CookieIdentityExtention {
|
struct CookieIdentityExtention {
|
||||||
login_timestamp: Option<SystemTime>
|
login_timestamp: Option<SystemTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CookieIdentityInner {
|
impl CookieIdentityInner {
|
||||||
fn new(key: &[u8]) -> CookieIdentityInner {
|
fn new(key: &[u8]) -> CookieIdentityInner {
|
||||||
let key_v2: Vec<u8> = key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect();
|
let key_v2: Vec<u8> =
|
||||||
|
key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect();
|
||||||
CookieIdentityInner {
|
CookieIdentityInner {
|
||||||
key: Key::from_master(key),
|
key: Key::from_master(key),
|
||||||
key_v2: Key::from_master(&key_v2),
|
key_v2: Key::from_master(&key_v2),
|
||||||
@ -334,12 +335,15 @@ impl CookieIdentityInner {
|
|||||||
value: Option<CookieValue>,
|
value: Option<CookieValue>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let add_cookie = value.is_some();
|
let add_cookie = value.is_some();
|
||||||
let val = value.map(|val| if !self.legacy_supported() {
|
let val = value.map(|val| {
|
||||||
|
if !self.legacy_supported() {
|
||||||
serde_json::to_string(&val)
|
serde_json::to_string(&val)
|
||||||
} else {
|
} else {
|
||||||
Ok(val.identity)
|
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_path(self.path.clone());
|
||||||
cookie.set_secure(self.secure);
|
cookie.set_secure(self.secure);
|
||||||
cookie.set_http_only(true);
|
cookie.set_http_only(true);
|
||||||
@ -357,7 +361,11 @@ impl CookieIdentityInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut jar = CookieJar::new();
|
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 {
|
if add_cookie {
|
||||||
jar.private(&key).add(cookie);
|
jar.private(&key).add(cookie);
|
||||||
} else {
|
} else {
|
||||||
@ -379,24 +387,32 @@ impl CookieIdentityInner {
|
|||||||
jar.private(&self.key).get(&self.name).map(|n| CookieValue {
|
jar.private(&self.key).get(&self.name).map(|n| CookieValue {
|
||||||
identity: n.value().to_string(),
|
identity: n.value().to_string(),
|
||||||
login_timestamp: None,
|
login_timestamp: None,
|
||||||
visit_timestamp: None
|
visit_timestamp: None,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
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<CookieValue> {
|
fn parse(&self, cookie: Cookie) -> Option<CookieValue> {
|
||||||
let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
|
let value: CookieValue = serde_json::from_str(cookie.value()).ok()?;
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
if let Some(visit_deadline) = self.visit_deadline {
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(login_deadline) = self.login_deadline {
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -513,12 +529,19 @@ impl IdentityPolicy for CookieIdentityPolicy {
|
|||||||
type ResponseFuture = 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).map(|CookieValue {identity, login_timestamp, ..}| {
|
Ok(self.0.load(req).map(
|
||||||
|
|CookieValue {
|
||||||
|
identity,
|
||||||
|
login_timestamp,
|
||||||
|
..
|
||||||
|
}| {
|
||||||
if self.0.requires_oob_data() {
|
if self.0.requires_oob_data() {
|
||||||
req.extensions_mut().insert(CookieIdentityExtention { login_timestamp });
|
req.extensions_mut()
|
||||||
|
.insert(CookieIdentityExtention { login_timestamp });
|
||||||
}
|
}
|
||||||
identity
|
identity
|
||||||
}))
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_response<B>(
|
fn to_response<B>(
|
||||||
@ -529,23 +552,31 @@ impl IdentityPolicy for CookieIdentityPolicy {
|
|||||||
) -> Self::ResponseFuture {
|
) -> Self::ResponseFuture {
|
||||||
let _ = if changed {
|
let _ = if changed {
|
||||||
let login_timestamp = SystemTime::now();
|
let login_timestamp = SystemTime::now();
|
||||||
self.0.set_cookie(res, id.map(|identity| CookieValue {
|
self.0.set_cookie(
|
||||||
|
res,
|
||||||
|
id.map(|identity| CookieValue {
|
||||||
identity,
|
identity,
|
||||||
login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
|
login_timestamp: self.0.login_deadline.map(|_| login_timestamp),
|
||||||
visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp)
|
visit_timestamp: self.0.visit_deadline.map(|_| login_timestamp),
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
} else if self.0.always_update_cookie() && id.is_some() {
|
} else if self.0.always_update_cookie() && id.is_some() {
|
||||||
let visit_timestamp = SystemTime::now();
|
let visit_timestamp = SystemTime::now();
|
||||||
let mut login_timestamp = None;
|
let mut login_timestamp = None;
|
||||||
if self.0.requires_oob_data() {
|
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;
|
login_timestamp = lt;
|
||||||
}
|
}
|
||||||
self.0.set_cookie(res, Some(CookieValue {
|
self.0.set_cookie(
|
||||||
|
res,
|
||||||
|
Some(CookieValue {
|
||||||
identity: id.unwrap(),
|
identity: id.unwrap(),
|
||||||
login_timestamp,
|
login_timestamp,
|
||||||
visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp)
|
visit_timestamp: self.0.visit_deadline.map(|_| visit_timestamp),
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
@ -676,34 +707,59 @@ mod tests {
|
|||||||
assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
|
assert_eq!(Duration::seconds(seconds as i64), c.max_age().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_identity_server<F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static>(f: F) -> impl actix_service::Service<Request = actix_http::Request, Response = ServiceResponse<actix_http::body::Body>, 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<actix_http::body::Body>,
|
||||||
|
Error = actix_http::Error,
|
||||||
|
> {
|
||||||
test::init_service(
|
test::init_service(
|
||||||
App::new()
|
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| {
|
.service(web::resource("/").to(|id: Identity| {
|
||||||
let identity = id.identity();
|
let identity = id.identity();
|
||||||
if identity.is_none() {
|
if identity.is_none() {
|
||||||
id.remember(COOKIE_LOGIN.to_string())
|
id.remember(COOKIE_LOGIN.to_string())
|
||||||
}
|
}
|
||||||
web::Json(identity)
|
web::Json(identity)
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
|
fn legacy_login_cookie(identity: &'static str) -> Cookie<'static> {
|
||||||
let mut jar = CookieJar::new();
|
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()
|
jar.get(COOKIE_NAME).unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn login_cookie(identity: &'static str, login_timestamp: Option<SystemTime>, visit_timestamp: Option<SystemTime>) -> Cookie<'static> {
|
fn login_cookie(
|
||||||
|
identity: &'static str,
|
||||||
|
login_timestamp: Option<SystemTime>,
|
||||||
|
visit_timestamp: Option<SystemTime>,
|
||||||
|
) -> Cookie<'static> {
|
||||||
let mut jar = CookieJar::new();
|
let mut jar = CookieJar::new();
|
||||||
let key: Vec<u8> = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect();
|
let key: Vec<u8> = COOKIE_KEY_MASTER
|
||||||
jar.private(&Key::from_master(&key)).add(Cookie::new(COOKIE_NAME, serde_json::to_string(&CookieValue {
|
.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(),
|
identity: identity.to_string(),
|
||||||
login_timestamp,
|
login_timestamp,
|
||||||
visit_timestamp
|
visit_timestamp,
|
||||||
}).unwrap()));
|
})
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
jar.get(COOKIE_NAME).unwrap().clone()
|
jar.get(COOKIE_NAME).unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,40 +781,63 @@ mod tests {
|
|||||||
for cookie in response.headers().get_all(header::SET_COOKIE) {
|
for cookie in response.headers().get_all(header::SET_COOKIE) {
|
||||||
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
|
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);
|
assert_eq!(cookie.value(), identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LoginTimestampCheck {
|
enum LoginTimestampCheck {
|
||||||
NoTimestamp,
|
NoTimestamp,
|
||||||
NewTimestamp,
|
NewTimestamp,
|
||||||
OldTimestamp(SystemTime)
|
OldTimestamp(SystemTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VisitTimeStampCheck {
|
enum VisitTimeStampCheck {
|
||||||
NoTimestamp,
|
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();
|
let mut cookies = CookieJar::new();
|
||||||
for cookie in response.headers().get_all(header::SET_COOKIE) {
|
for cookie in response.headers().get_all(header::SET_COOKIE) {
|
||||||
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
|
cookies.add(Cookie::parse(cookie.to_str().unwrap().to_string()).unwrap());
|
||||||
}
|
}
|
||||||
let key: Vec<u8> = COOKIE_KEY_MASTER.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect();
|
let key: Vec<u8> = COOKIE_KEY_MASTER
|
||||||
let cookie = cookies.private(&Key::from_master(&key)).get(COOKIE_NAME).unwrap();
|
.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();
|
let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
|
||||||
assert_eq!(cv.identity, identity);
|
assert_eq!(cv.identity, identity);
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
let t30sec_ago = now - Duration::seconds(30).to_std().unwrap();
|
let t30sec_ago = now - Duration::seconds(30).to_std().unwrap();
|
||||||
match login_timestamp {
|
match login_timestamp {
|
||||||
LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
|
LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
|
||||||
LoginTimestampCheck::NewTimestamp => assert!(t30sec_ago <= cv.login_timestamp.unwrap() && cv.login_timestamp.unwrap() <= now),
|
LoginTimestampCheck::NewTimestamp => assert!(
|
||||||
LoginTimestampCheck::OldTimestamp(old_timestamp) => assert_eq!(cv.login_timestamp, Some(old_timestamp))
|
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 {
|
match visit_timestamp {
|
||||||
VisitTimeStampCheck::NoTimestamp => assert_eq!(cv.visit_timestamp, None),
|
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]
|
#[test]
|
||||||
fn test_identity_legacy_cookie_is_set() {
|
fn test_identity_legacy_cookie_is_set() {
|
||||||
let mut srv = create_identity_server(|c| c);
|
let mut srv = create_identity_server(|c| c);
|
||||||
let mut resp = test::call_service(
|
let mut resp =
|
||||||
&mut srv,
|
test::call_service(&mut srv, TestRequest::with_uri("/").to_request());
|
||||||
TestRequest::with_uri("/")
|
|
||||||
.to_request()
|
|
||||||
);
|
|
||||||
assert_logged_in(&mut resp, None);
|
assert_logged_in(&mut resp, None);
|
||||||
assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
|
assert_legacy_login_cookie(&mut resp, COOKIE_LOGIN);
|
||||||
}
|
}
|
||||||
@ -790,7 +866,7 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
|
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
|
||||||
assert_no_login_cookie(&mut resp);
|
assert_no_login_cookie(&mut resp);
|
||||||
@ -804,10 +880,15 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
@ -818,10 +899,15 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
@ -832,10 +918,15 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
@ -846,38 +937,61 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
|
fn test_identity_cookie_rejected_if_login_timestamp_too_old() {
|
||||||
let mut srv = create_identity_server(|c| c.login_deadline(Duration::days(90)));
|
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(
|
let mut resp = test::call_service(
|
||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
|
fn test_identity_cookie_rejected_if_visit_timestamp_too_old() {
|
||||||
let mut srv = create_identity_server(|c| c.visit_deadline(Duration::days(90)));
|
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(
|
let mut resp = test::call_service(
|
||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, None);
|
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]
|
#[test]
|
||||||
@ -888,7 +1002,7 @@ mod tests {
|
|||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
|
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
|
||||||
assert_no_login_cookie(&mut resp);
|
assert_no_login_cookie(&mut resp);
|
||||||
@ -896,16 +1010,24 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_identity_cookie_updated_on_visit_deadline() {
|
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 timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap();
|
||||||
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
|
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
|
||||||
let mut resp = test::call_service(
|
let mut resp = test::call_service(
|
||||||
&mut srv,
|
&mut srv,
|
||||||
TestRequest::with_uri("/")
|
TestRequest::with_uri("/")
|
||||||
.cookie(cookie.clone())
|
.cookie(cookie.clone())
|
||||||
.to_request()
|
.to_request(),
|
||||||
);
|
);
|
||||||
assert_logged_in(&mut resp, Some(COOKIE_LOGIN));
|
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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
135
src/service.rs
135
src/service.rs
@ -7,11 +7,14 @@ use actix_http::{
|
|||||||
Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response,
|
Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response,
|
||||||
ResponseHead,
|
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 futures::future::{ok, FutureResult, IntoFuture};
|
||||||
|
|
||||||
use crate::config::{AppConfig, AppService};
|
use crate::config::{AppConfig, AppService};
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
|
use crate::dev::insert_slash;
|
||||||
|
use crate::guard::Guard;
|
||||||
use crate::info::ConnectionInfo;
|
use crate::info::ConnectionInfo;
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
|
||||||
@ -380,10 +383,136 @@ impl<B: MessageBody> fmt::Debug for ServiceResponse<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WebService {
|
||||||
|
rdef: String,
|
||||||
|
name: Option<String>,
|
||||||
|
guards: Vec<Box<Guard>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||||
|
self.guards.push(Box::new(guard));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a service factory implementation and generate web service.
|
||||||
|
pub fn finish<T, F>(self, service: F) -> impl HttpServiceFactory
|
||||||
|
where
|
||||||
|
F: IntoNewService<T>,
|
||||||
|
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<T> {
|
||||||
|
srv: T,
|
||||||
|
rdef: String,
|
||||||
|
name: Option<String>,
|
||||||
|
guards: Vec<Box<Guard>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HttpServiceFactory for WebServiceImpl<T>
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::test::TestRequest;
|
use super::*;
|
||||||
use crate::HttpResponse;
|
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]
|
#[test]
|
||||||
fn test_fmt_debug() {
|
fn test_fmt_debug() {
|
||||||
|
23
src/web.rs
23
src/web.rs
@ -12,6 +12,7 @@ use crate::resource::Resource;
|
|||||||
use crate::responder::Responder;
|
use crate::responder::Responder;
|
||||||
use crate::route::Route;
|
use crate::route::Route;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
use crate::service::WebService;
|
||||||
|
|
||||||
pub use crate::config::ServiceConfig;
|
pub use crate::config::ServiceConfig;
|
||||||
pub use crate::data::{Data, RouteData};
|
pub use crate::data::{Data, RouteData};
|
||||||
@ -274,6 +275,28 @@ where
|
|||||||
Route::new().to_async(handler)
|
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
|
/// Execute blocking function on a thread pool, returns future that resolves
|
||||||
/// to result of the function execution.
|
/// to result of the function execution.
|
||||||
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
|
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
|
||||||
|
Loading…
Reference in New Issue
Block a user