mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 07:53:00 +01:00
add resource map, it allow to check if router has resource and it allows to generate urls for named resources
This commit is contained in:
parent
2f6df11183
commit
aadcdaa3d6
@ -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-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 = { git = "https://github.com/actix/actix-net.git" }
|
||||||
|
#actix-router = { path="../actix-net/router" }
|
||||||
|
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
derive_more = "0.14"
|
derive_more = "0.14"
|
||||||
encoding = "0.2"
|
encoding = "0.2"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
|
hashbrown = "0.1.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lazy_static = "1.2"
|
lazy_static = "1.2"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
@ -89,6 +91,7 @@ serde_json = "1.0"
|
|||||||
serde_urlencoded = "^0.5.3"
|
serde_urlencoded = "^0.5.3"
|
||||||
threadpool = "1.7"
|
threadpool = "1.7"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
|
|
||||||
# middlewares
|
# middlewares
|
||||||
# actix-session = { path="session", optional = true }
|
# actix-session = { path="session", optional = true }
|
||||||
|
@ -1090,4 +1090,35 @@ mod tests {
|
|||||||
// assert_eq!(response.status(), StatusCode::OK);
|
// 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"]))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
24
src/app.rs
24
src/app.rs
@ -16,6 +16,7 @@ use futures::{Async, Future, IntoFuture, Poll};
|
|||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
use crate::route::Route;
|
use crate::route::Route;
|
||||||
use crate::service::{
|
use crate::service::{
|
||||||
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||||
@ -449,19 +450,29 @@ where
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|mut srv| srv.register(&mut config));
|
.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 {
|
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
|
||||||
default,
|
default,
|
||||||
services: Rc::new(
|
services: Rc::new(
|
||||||
config
|
config
|
||||||
.into_services()
|
.into_services()
|
||||||
.into_iter()
|
.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(),
|
.collect(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// complete ResourceMap tree creation
|
||||||
|
let rmap = Rc::new(rmap);
|
||||||
|
rmap.finish(rmap.clone());
|
||||||
|
|
||||||
AppInit {
|
AppInit {
|
||||||
|
rmap,
|
||||||
chain: self.chain,
|
chain: self.chain,
|
||||||
state: self.state,
|
state: self.state,
|
||||||
extensions: Rc::new(RefCell::new(Rc::new(self.extensions))),
|
extensions: Rc::new(RefCell::new(Rc::new(self.extensions))),
|
||||||
@ -561,8 +572,7 @@ impl<P> Future for AppRoutingFactoryResponse<P> {
|
|||||||
.fold(Router::build(), |mut router, item| {
|
.fold(Router::build(), |mut router, item| {
|
||||||
match item {
|
match item {
|
||||||
CreateAppRoutingItem::Service(path, guards, service) => {
|
CreateAppRoutingItem::Service(path, guards, service) => {
|
||||||
router.rdef(path, service);
|
router.rdef(path, service).2 = guards;
|
||||||
router.set_user_data(guards);
|
|
||||||
}
|
}
|
||||||
CreateAppRoutingItem::Future(_, _, _) => unreachable!(),
|
CreateAppRoutingItem::Future(_, _, _) => unreachable!(),
|
||||||
}
|
}
|
||||||
@ -683,6 +693,7 @@ where
|
|||||||
C: NewService<ServiceRequest, Response = ServiceRequest<P>>,
|
C: NewService<ServiceRequest, Response = ServiceRequest<P>>,
|
||||||
{
|
{
|
||||||
chain: C,
|
chain: C,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
state: Vec<Box<StateFactory>>,
|
state: Vec<Box<StateFactory>>,
|
||||||
extensions: Rc<RefCell<Rc<Extensions>>>,
|
extensions: Rc<RefCell<Rc<Extensions>>>,
|
||||||
}
|
}
|
||||||
@ -702,6 +713,7 @@ where
|
|||||||
chain: self.chain.new_service(&()),
|
chain: self.chain.new_service(&()),
|
||||||
state: self.state.iter().map(|s| s.construct()).collect(),
|
state: self.state.iter().map(|s| s.construct()).collect(),
|
||||||
extensions: self.extensions.clone(),
|
extensions: self.extensions.clone(),
|
||||||
|
rmap: self.rmap.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -712,6 +724,7 @@ where
|
|||||||
C: NewService<ServiceRequest, Response = ServiceRequest<P>, InitError = ()>,
|
C: NewService<ServiceRequest, Response = ServiceRequest<P>, InitError = ()>,
|
||||||
{
|
{
|
||||||
chain: C::Future,
|
chain: C::Future,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
state: Vec<Box<StateFactoryResult>>,
|
state: Vec<Box<StateFactoryResult>>,
|
||||||
extensions: Rc<RefCell<Rc<Extensions>>>,
|
extensions: Rc<RefCell<Rc<Extensions>>>,
|
||||||
}
|
}
|
||||||
@ -744,6 +757,7 @@ where
|
|||||||
|
|
||||||
Ok(Async::Ready(AppInitService {
|
Ok(Async::Ready(AppInitService {
|
||||||
chain,
|
chain,
|
||||||
|
rmap: self.rmap.clone(),
|
||||||
extensions: self.extensions.borrow().clone(),
|
extensions: self.extensions.borrow().clone(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -755,6 +769,7 @@ where
|
|||||||
C: Service<ServiceRequest, Response = ServiceRequest<P>>,
|
C: Service<ServiceRequest, Response = ServiceRequest<P>>,
|
||||||
{
|
{
|
||||||
chain: C,
|
chain: C,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
extensions: Rc<Extensions>,
|
extensions: Rc<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,6 +789,7 @@ where
|
|||||||
let req = ServiceRequest::new(
|
let req = ServiceRequest::new(
|
||||||
Path::new(Url::new(req.uri().clone())),
|
Path::new(Url::new(req.uri().clone())),
|
||||||
req,
|
req,
|
||||||
|
self.rmap.clone(),
|
||||||
self.extensions.clone(),
|
self.extensions.clone(),
|
||||||
);
|
);
|
||||||
self.chain.call(req)
|
self.chain.call(req)
|
||||||
|
@ -5,6 +5,7 @@ use actix_router::ResourceDef;
|
|||||||
use actix_service::{boxed, IntoNewService, NewService};
|
use actix_service::{boxed, IntoNewService, NewService};
|
||||||
|
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
use crate::service::{ServiceRequest, ServiceResponse};
|
use crate::service::{ServiceRequest, ServiceResponse};
|
||||||
|
|
||||||
type Guards = Vec<Box<Guard>>;
|
type Guards = Vec<Box<Guard>>;
|
||||||
@ -18,7 +19,12 @@ pub struct AppConfig<P> {
|
|||||||
host: String,
|
host: String,
|
||||||
root: bool,
|
root: bool,
|
||||||
default: Rc<HttpNewService<P>>,
|
default: Rc<HttpNewService<P>>,
|
||||||
services: Vec<(ResourceDef, HttpNewService<P>, Option<Guards>)>,
|
services: Vec<(
|
||||||
|
ResourceDef,
|
||||||
|
HttpNewService<P>,
|
||||||
|
Option<Guards>,
|
||||||
|
Option<Rc<ResourceMap>>,
|
||||||
|
)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: 'static> AppConfig<P> {
|
impl<P: 'static> AppConfig<P> {
|
||||||
@ -46,7 +52,12 @@ impl<P: 'static> AppConfig<P> {
|
|||||||
|
|
||||||
pub(crate) fn into_services(
|
pub(crate) fn into_services(
|
||||||
self,
|
self,
|
||||||
) -> Vec<(ResourceDef, HttpNewService<P>, Option<Guards>)> {
|
) -> Vec<(
|
||||||
|
ResourceDef,
|
||||||
|
HttpNewService<P>,
|
||||||
|
Option<Guards>,
|
||||||
|
Option<Rc<ResourceMap>>,
|
||||||
|
)> {
|
||||||
self.services
|
self.services
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +96,7 @@ impl<P: 'static> AppConfig<P> {
|
|||||||
rdef: ResourceDef,
|
rdef: ResourceDef,
|
||||||
guards: Option<Vec<Box<Guard>>>,
|
guards: Option<Vec<Box<Guard>>>,
|
||||||
service: F,
|
service: F,
|
||||||
|
nested: Option<Rc<ResourceMap>>,
|
||||||
) where
|
) where
|
||||||
F: IntoNewService<S, ServiceRequest<P>>,
|
F: IntoNewService<S, ServiceRequest<P>>,
|
||||||
S: NewService<
|
S: NewService<
|
||||||
@ -98,6 +110,7 @@ impl<P: 'static> AppConfig<P> {
|
|||||||
rdef,
|
rdef,
|
||||||
boxed::new_service(service.into_new_service()),
|
boxed::new_service(service.into_new_service()),
|
||||||
guards,
|
guards,
|
||||||
|
nested,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/error.rs
Normal file
20
src/error.rs
Normal file
@ -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 {}
|
@ -6,11 +6,13 @@ mod handler;
|
|||||||
// mod info;
|
// mod info;
|
||||||
pub mod blocking;
|
pub mod blocking;
|
||||||
mod config;
|
mod config;
|
||||||
|
pub mod error;
|
||||||
pub mod guard;
|
pub mod guard;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
mod request;
|
mod request;
|
||||||
mod resource;
|
mod resource;
|
||||||
mod responder;
|
mod responder;
|
||||||
|
mod rmap;
|
||||||
mod route;
|
mod route;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod server;
|
mod server;
|
||||||
@ -27,7 +29,7 @@ pub use actix_web_codegen::*;
|
|||||||
|
|
||||||
// re-export for convenience
|
// re-export for convenience
|
||||||
pub use actix_http::Response as HttpResponse;
|
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::app::App;
|
||||||
pub use crate::extract::FromRequest;
|
pub use crate::extract::FromRequest;
|
||||||
|
@ -7,7 +7,9 @@ use actix_http::http::{HeaderMap, Method, Uri, Version};
|
|||||||
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
||||||
use actix_router::{Path, Url};
|
use actix_router::{Path, Url};
|
||||||
|
|
||||||
|
use crate::error::UrlGenerationError;
|
||||||
use crate::extract::FromRequest;
|
use crate::extract::FromRequest;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
use crate::service::ServiceFromRequest;
|
use crate::service::ServiceFromRequest;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -15,6 +17,7 @@ use crate::service::ServiceFromRequest;
|
|||||||
pub struct HttpRequest {
|
pub struct HttpRequest {
|
||||||
pub(crate) head: Message<RequestHead>,
|
pub(crate) head: Message<RequestHead>,
|
||||||
pub(crate) path: Path<Url>,
|
pub(crate) path: Path<Url>,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
extensions: Rc<Extensions>,
|
extensions: Rc<Extensions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,11 +26,13 @@ impl HttpRequest {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
head: Message<RequestHead>,
|
head: Message<RequestHead>,
|
||||||
path: Path<Url>,
|
path: Path<Url>,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
extensions: Rc<Extensions>,
|
extensions: Rc<Extensions>,
|
||||||
) -> HttpRequest {
|
) -> HttpRequest {
|
||||||
HttpRequest {
|
HttpRequest {
|
||||||
head,
|
head,
|
||||||
path,
|
path,
|
||||||
|
rmap,
|
||||||
extensions,
|
extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +98,47 @@ impl HttpRequest {
|
|||||||
&self.extensions
|
&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<U, I>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
elements: U,
|
||||||
|
) -> Result<url::Url, UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: IntoIterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
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<url::Url, UrlGenerationError> {
|
||||||
|
const NO_PARAMS: [&str; 0] = [];
|
||||||
|
self.url_for(name, &NO_PARAMS)
|
||||||
|
}
|
||||||
|
|
||||||
// /// Get *ConnectionInfo* for the correct request.
|
// /// Get *ConnectionInfo* for the correct request.
|
||||||
// #[inline]
|
// #[inline]
|
||||||
// pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
// pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||||
|
@ -288,7 +288,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
ResourceDef::new(&self.rdef)
|
ResourceDef::new(&self.rdef)
|
||||||
};
|
};
|
||||||
config.register_service(rdef, guards, self)
|
config.register_service(rdef, guards, self, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
188
src/rmap.rs
Normal file
188
src/rmap.rs
Normal file
@ -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<Option<Rc<ResourceMap>>>,
|
||||||
|
named: HashMap<String, ResourceDef>,
|
||||||
|
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Rc<ResourceMap>>) {
|
||||||
|
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<ResourceMap>) {
|
||||||
|
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<U, I>(
|
||||||
|
&self,
|
||||||
|
req: &HttpRequest,
|
||||||
|
name: &str,
|
||||||
|
elements: U,
|
||||||
|
) -> Result<Url, UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: IntoIterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
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<U, I>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
path: &mut String,
|
||||||
|
elements: &mut U,
|
||||||
|
) -> Result<Option<()>, UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: Iterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
if self.pattern_for(name, path, elements)?.is_some() {
|
||||||
|
Ok(Some(()))
|
||||||
|
} else {
|
||||||
|
self.parent_pattern_for(name, path, elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pattern_for<U, I>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
path: &mut String,
|
||||||
|
elements: &mut U,
|
||||||
|
) -> Result<Option<()>, UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: Iterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
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<U, I>(
|
||||||
|
&self,
|
||||||
|
path: &mut String,
|
||||||
|
elements: &mut U,
|
||||||
|
) -> Result<(), UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: Iterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
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<U, I>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
path: &mut String,
|
||||||
|
elements: &mut U,
|
||||||
|
) -> Result<Option<()>, UrlGenerationError>
|
||||||
|
where
|
||||||
|
U: Iterator<Item = I>,
|
||||||
|
I: AsRef<str>,
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/scope.rs
19
src/scope.rs
@ -13,6 +13,7 @@ use futures::{Async, Poll};
|
|||||||
use crate::dev::{AppConfig, HttpServiceFactory};
|
use crate::dev::{AppConfig, HttpServiceFactory};
|
||||||
use crate::guard::Guard;
|
use crate::guard::Guard;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
use crate::route::Route;
|
use crate::route::Route;
|
||||||
use crate::service::{
|
use crate::service::{
|
||||||
ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
|
ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
|
||||||
@ -237,35 +238,46 @@ where
|
|||||||
> + 'static,
|
> + 'static,
|
||||||
{
|
{
|
||||||
fn register(self, config: &mut AppConfig<P>) {
|
fn register(self, config: &mut AppConfig<P>) {
|
||||||
|
// update default resource if needed
|
||||||
if self.default.borrow().is_none() {
|
if self.default.borrow().is_none() {
|
||||||
*self.default.borrow_mut() = Some(config.default_service());
|
*self.default.borrow_mut() = Some(config.default_service());
|
||||||
}
|
}
|
||||||
|
|
||||||
// register services
|
// register nested services
|
||||||
let mut cfg = config.clone_config();
|
let mut cfg = config.clone_config();
|
||||||
self.services
|
self.services
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|mut srv| srv.register(&mut cfg));
|
.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 {
|
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
|
||||||
default: self.default.clone(),
|
default: self.default.clone(),
|
||||||
services: Rc::new(
|
services: Rc::new(
|
||||||
cfg.into_services()
|
cfg.into_services()
|
||||||
.into_iter()
|
.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(),
|
.collect(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// get guards
|
||||||
let guards = if self.guards.is_empty() {
|
let guards = if self.guards.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.guards)
|
Some(self.guards)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// register final service
|
||||||
config.register_service(
|
config.register_service(
|
||||||
ResourceDef::root_prefix(&self.rdef),
|
ResourceDef::root_prefix(&self.rdef),
|
||||||
guards,
|
guards,
|
||||||
self.endpoint,
|
self.endpoint,
|
||||||
|
Some(Rc::new(rmap)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,8 +379,7 @@ impl<P> Future for ScopeFactoryResponse<P> {
|
|||||||
.fold(Router::build(), |mut router, item| {
|
.fold(Router::build(), |mut router, item| {
|
||||||
match item {
|
match item {
|
||||||
CreateScopeServiceItem::Service(path, guards, service) => {
|
CreateScopeServiceItem::Service(path, guards, service) => {
|
||||||
router.rdef(path, service);
|
router.rdef(path, service).2 = guards;
|
||||||
router.set_user_data(guards);
|
|
||||||
}
|
}
|
||||||
CreateScopeServiceItem::Future(_, _, _) => unreachable!(),
|
CreateScopeServiceItem::Future(_, _, _) => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ where
|
|||||||
///
|
///
|
||||||
/// HttpServer does not change any configuration for TcpListener,
|
/// HttpServer does not change any configuration for TcpListener,
|
||||||
/// it needs to be configured before passing it to listen() method.
|
/// 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<Self> {
|
||||||
let cfg = self.config.clone();
|
let cfg = self.config.clone();
|
||||||
let factory = self.factory.clone();
|
let factory = self.factory.clone();
|
||||||
let addr = lst.local_addr().unwrap();
|
let addr = lst.local_addr().unwrap();
|
||||||
@ -248,9 +248,9 @@ where
|
|||||||
ServiceConfig::new(c.keep_alive, c.client_timeout, 0);
|
ServiceConfig::new(c.keep_alive, c.client_timeout, 0);
|
||||||
HttpService::with_config(service_config, factory())
|
HttpService::with_config(service_config, factory())
|
||||||
},
|
},
|
||||||
));
|
)?);
|
||||||
|
|
||||||
self
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
@ -328,7 +328,7 @@ where
|
|||||||
let sockets = self.bind2(addr)?;
|
let sockets = self.bind2(addr)?;
|
||||||
|
|
||||||
for lst in sockets {
|
for lst in sockets {
|
||||||
self = self.listen(lst);
|
self = self.listen(lst)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
@ -15,6 +15,7 @@ use futures::future::{ok, FutureResult, IntoFuture};
|
|||||||
|
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
|
|
||||||
pub trait HttpServiceFactory<P> {
|
pub trait HttpServiceFactory<P> {
|
||||||
fn register(self, config: &mut AppConfig<P>);
|
fn register(self, config: &mut AppConfig<P>);
|
||||||
@ -58,12 +59,13 @@ impl<P> ServiceRequest<P> {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
path: Path<Url>,
|
path: Path<Url>,
|
||||||
request: Request<P>,
|
request: Request<P>,
|
||||||
|
rmap: Rc<ResourceMap>,
|
||||||
extensions: Rc<Extensions>,
|
extensions: Rc<Extensions>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (head, payload) = request.into_parts();
|
let (head, payload) = request.into_parts();
|
||||||
ServiceRequest {
|
ServiceRequest {
|
||||||
payload,
|
payload,
|
||||||
req: HttpRequest::new(head, path, extensions),
|
req: HttpRequest::new(head, path, rmap, extensions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
src/test.rs
13
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::http::{HttpTryFrom, Method, Version};
|
||||||
use actix_http::test::TestRequest as HttpTestRequest;
|
use actix_http::test::TestRequest as HttpTestRequest;
|
||||||
use actix_http::{Extensions, PayloadStream, Request};
|
use actix_http::{Extensions, PayloadStream, Request};
|
||||||
use actix_router::{Path, Url};
|
use actix_router::{Path, ResourceDef, Url};
|
||||||
use actix_rt::Runtime;
|
use actix_rt::Runtime;
|
||||||
use actix_service::{IntoNewService, NewService, Service};
|
use actix_service::{IntoNewService, NewService, Service};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
use crate::rmap::ResourceMap;
|
||||||
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@ -135,6 +136,7 @@ where
|
|||||||
pub struct TestRequest {
|
pub struct TestRequest {
|
||||||
req: HttpTestRequest,
|
req: HttpTestRequest,
|
||||||
extensions: Extensions,
|
extensions: Extensions,
|
||||||
|
rmap: ResourceMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestRequest {
|
impl Default for TestRequest {
|
||||||
@ -142,6 +144,7 @@ impl Default for TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default(),
|
req: HttpTestRequest::default(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,6 +155,7 @@ impl TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default().uri(path).take(),
|
req: HttpTestRequest::default().uri(path).take(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +164,7 @@ impl TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default().set(hdr).take(),
|
req: HttpTestRequest::default().set(hdr).take(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +177,7 @@ impl TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default().header(key, value).take(),
|
req: HttpTestRequest::default().header(key, value).take(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +186,7 @@ impl TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default().method(Method::GET).take(),
|
req: HttpTestRequest::default().method(Method::GET).take(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +195,7 @@ impl TestRequest {
|
|||||||
TestRequest {
|
TestRequest {
|
||||||
req: HttpTestRequest::default().method(Method::POST).take(),
|
req: HttpTestRequest::default().method(Method::POST).take(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
rmap: ResourceMap::new(ResourceDef::new("")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,6 +252,7 @@ impl TestRequest {
|
|||||||
ServiceRequest::new(
|
ServiceRequest::new(
|
||||||
Path::new(Url::new(req.uri().clone())),
|
Path::new(Url::new(req.uri().clone())),
|
||||||
req,
|
req,
|
||||||
|
Rc::new(self.rmap),
|
||||||
Rc::new(self.extensions),
|
Rc::new(self.extensions),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -260,6 +269,7 @@ impl TestRequest {
|
|||||||
ServiceRequest::new(
|
ServiceRequest::new(
|
||||||
Path::new(Url::new(req.uri().clone())),
|
Path::new(Url::new(req.uri().clone())),
|
||||||
req,
|
req,
|
||||||
|
Rc::new(self.rmap),
|
||||||
Rc::new(self.extensions),
|
Rc::new(self.extensions),
|
||||||
)
|
)
|
||||||
.into_request()
|
.into_request()
|
||||||
@ -272,6 +282,7 @@ impl TestRequest {
|
|||||||
let req = ServiceRequest::new(
|
let req = ServiceRequest::new(
|
||||||
Path::new(Url::new(req.uri().clone())),
|
Path::new(Url::new(req.uri().clone())),
|
||||||
req,
|
req,
|
||||||
|
Rc::new(self.rmap),
|
||||||
Rc::new(self.extensions),
|
Rc::new(self.extensions),
|
||||||
);
|
);
|
||||||
ServiceFromRequest::new(req, None)
|
ServiceFromRequest::new(req, None)
|
||||||
|
Loading…
Reference in New Issue
Block a user