mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 07:53:00 +01:00
add raw services support
This commit is contained in:
parent
7300002226
commit
3b3dbb4f40
@ -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
|
||||
|
@ -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;
|
||||
|
@ -308,12 +308,13 @@ struct CookieValue {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CookieIdentityExtention {
|
||||
login_timestamp: Option<SystemTime>
|
||||
login_timestamp: Option<SystemTime>,
|
||||
}
|
||||
|
||||
impl 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 {
|
||||
key: Key::from_master(key),
|
||||
key_v2: Key::from_master(&key_v2),
|
||||
@ -334,12 +335,15 @@ impl CookieIdentityInner {
|
||||
value: Option<CookieValue>,
|
||||
) -> Result<()> {
|
||||
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)
|
||||
} 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<CookieValue> {
|
||||
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, ..}| {
|
||||
Ok(self.0.load(req).map(
|
||||
|CookieValue {
|
||||
identity,
|
||||
login_timestamp,
|
||||
..
|
||||
}| {
|
||||
if self.0.requires_oob_data() {
|
||||
req.extensions_mut().insert(CookieIdentityExtention { login_timestamp });
|
||||
req.extensions_mut()
|
||||
.insert(CookieIdentityExtention { login_timestamp });
|
||||
}
|
||||
identity
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn to_response<B>(
|
||||
@ -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 {
|
||||
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)
|
||||
}))
|
||||
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 {
|
||||
self.0.set_cookie(
|
||||
res,
|
||||
Some(CookieValue {
|
||||
identity: id.unwrap(),
|
||||
login_timestamp,
|
||||
visit_timestamp: self.0.visit_deadline.map(|_| visit_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<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(
|
||||
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<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 key: Vec<u8> = 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 {
|
||||
let key: Vec<u8> = 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()));
|
||||
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<u8> = 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<u8> = 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
135
src/service.rs
135
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<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)]
|
||||
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() {
|
||||
|
23
src/web.rs
23
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, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
|
||||
|
Loading…
Reference in New Issue
Block a user