1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-12-30 12:00:38 +00:00

add external resource support on scope level

This commit is contained in:
Nikolay Kim 2019-06-03 23:41:32 +06:00
parent 1fce4876f3
commit 0e138e111f
4 changed files with 105 additions and 75 deletions

View file

@ -1,9 +1,11 @@
# Changes # Changes
## [1.0.0] - 2019-05-xx ## [1.0.0] - 2019-06-xx
### Add ### Add
* Add `Scope::configure()` method.
* Add `ServiceRequest::set_payload()` method. * Add `ServiceRequest::set_payload()` method.
* Add `test::TestRequest::set_json()` convenience method to automatically * Add `test::TestRequest::set_json()` convenience method to automatically

View file

@ -102,7 +102,7 @@ rustls = { version = "0.15", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "0.2.2", 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-http-test = { version = "0.2.0", features=["ssl"] }
actix-files = { version = "0.1.0" } actix-files = { version = "0.1.1" }
rand = "0.6" rand = "0.6"
env_logger = "0.6" env_logger = "0.6"
serde_derive = "1.0" serde_derive = "1.0"

View file

@ -122,7 +122,9 @@ impl ResourceMap {
I: AsRef<str>, I: AsRef<str>,
{ {
if let Some(pattern) = self.named.get(name) { if let Some(pattern) = self.named.get(name) {
if pattern.pattern().starts_with("/") {
self.fill_root(path, elements)?; self.fill_root(path, elements)?;
}
if pattern.resource_path(path, elements) { if pattern.resource_path(path, elements) {
Ok(Some(())) Ok(Some(()))
} else { } else {

View file

@ -11,6 +11,7 @@ use actix_service::{
use futures::future::{ok, Either, Future, FutureResult}; use futures::future::{ok, Either, Future, FutureResult};
use futures::{Async, IntoFuture, Poll}; use futures::{Async, IntoFuture, Poll};
use crate::config::ServiceConfig;
use crate::data::Data; use crate::data::Data;
use crate::dev::{AppService, HttpServiceFactory}; use crate::dev::{AppService, HttpServiceFactory};
use crate::error::Error; use crate::error::Error;
@ -21,7 +22,6 @@ use crate::route::Route;
use crate::service::{ use crate::service::{
ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
}; };
use crate::config::ServiceConfig;
type Guards = Vec<Box<Guard>>; type Guards = Vec<Box<Guard>>;
type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>; type HttpService = BoxedService<ServiceRequest, ServiceResponse, Error>;
@ -67,6 +67,7 @@ pub struct Scope<T = ScopeEndpoint> {
services: Vec<Box<ServiceFactory>>, services: Vec<Box<ServiceFactory>>,
guards: Vec<Box<Guard>>, guards: Vec<Box<Guard>>,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
external: Vec<ResourceDef>,
factory_ref: Rc<RefCell<Option<ScopeFactory>>>, factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
} }
@ -81,59 +82,10 @@ impl Scope {
guards: Vec::new(), guards: Vec::new(),
services: Vec::new(), services: Vec::new(),
default: Rc::new(RefCell::new(None)), default: Rc::new(RefCell::new(None)),
external: Vec::new(),
factory_ref: fref, 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<F>(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<T> Scope<T> impl<T> Scope<T>
@ -204,6 +156,56 @@ where
self 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<F>(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. /// Register http service.
/// ///
/// This is similar to `App's` service registration. /// This is similar to `App's` service registration.
@ -332,6 +334,7 @@ where
guards: self.guards, guards: self.guards,
services: self.services, services: self.services,
default: self.default, default: self.default,
external: self.external,
factory_ref: self.factory_ref, factory_ref: self.factory_ref,
} }
} }
@ -410,6 +413,11 @@ where
let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); 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 // custom app data storage
if let Some(ref mut ext) = self.data { if let Some(ref mut ext) = self.data {
config.set_service_data(ext); config.set_service_data(ext);
@ -645,7 +653,7 @@ mod tests {
use crate::dev::{Body, ResponseBody}; use crate::dev::{Body, ResponseBody};
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse}; 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}; use crate::{guard, web, App, Error, HttpRequest, HttpResponse};
#[test] #[test]
@ -1076,14 +1084,10 @@ mod tests {
#[test] #[test]
fn test_scope_config() { fn test_scope_config() {
let mut srv = init_service( let mut srv =
App::new().service( init_service(App::new().service(web::scope("/app").configure(|s| {
web::scope("/app") s.route("/path1", web::get().to(|| HttpResponse::Ok()));
.configure(|s|{ })));
s.route("/path1", web::get().to(||HttpResponse::Ok()));
})
),
);
let req = TestRequest::with_uri("/app/path1").to_request(); let req = TestRequest::with_uri("/app/path1").to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
@ -1092,21 +1096,43 @@ mod tests {
#[test] #[test]
fn test_scope_config_2() { fn test_scope_config_2() {
let mut srv = init_service( let mut srv =
App::new().service( init_service(App::new().service(web::scope("/app").configure(|s| {
web::scope("/app") s.service(web::scope("/v1").configure(|s| {
.configure(|s|{ s.route("/", web::get().to(|| HttpResponse::Ok()));
s.service(
web::scope("/v1")
.configure(|s|{
s.route("/", web::get().to(||HttpResponse::Ok()));
})); }));
}) })));
),
);
let req = TestRequest::with_uri("/app/v1/").to_request(); let req = TestRequest::with_uri("/app/v1/").to_request();
let resp = block_on(srv.call(req)).unwrap(); let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK); 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"[..]);
}
} }