mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 16:02:59 +01:00
extend CorsBuilder api to make it more user friendly
This commit is contained in:
parent
d04ff13955
commit
1686682c19
@ -328,8 +328,15 @@ impl<S> App<S> where S: 'static {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default resource to be used if no matching route could be
|
/// Configure resource for a specific path.
|
||||||
/// found.
|
#[doc(hidden)]
|
||||||
|
pub fn register_resource(&mut self, path: &str, resource: ResourceHandler<S>) {
|
||||||
|
let pattern = Resource::new(resource.get_name(), path);
|
||||||
|
self.parts.as_mut().expect("Use after finish")
|
||||||
|
.resources.push((pattern, Some(resource)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default resource to be used if no matching route could be found.
|
||||||
pub fn default_resource<F, R>(mut self, f: F) -> App<S>
|
pub fn default_resource<F, R>(mut self, f: F) -> App<S>
|
||||||
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
|
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
|
||||||
{
|
{
|
||||||
|
@ -52,6 +52,7 @@ use std::rc::Rc;
|
|||||||
use http::{self, Method, HttpTryFrom, Uri, StatusCode};
|
use http::{self, Method, HttpTryFrom, Uri, StatusCode};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
|
|
||||||
|
use application::App;
|
||||||
use error::{Result, ResponseError};
|
use error::{Result, ResponseError};
|
||||||
use resource::ResourceHandler;
|
use resource::ResourceHandler;
|
||||||
use httpmessage::HttpMessage;
|
use httpmessage::HttpMessage;
|
||||||
@ -183,7 +184,7 @@ impl Default for Cors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cors {
|
impl Cors {
|
||||||
pub fn build() -> CorsBuilder {
|
pub fn build() -> CorsBuilder<()> {
|
||||||
CorsBuilder {
|
CorsBuilder {
|
||||||
cors: Some(Inner {
|
cors: Some(Inner {
|
||||||
origins: AllOrSome::All,
|
origins: AllOrSome::All,
|
||||||
@ -200,6 +201,48 @@ impl Cors {
|
|||||||
methods: false,
|
methods: false,
|
||||||
error: None,
|
error: None,
|
||||||
expose_hdrs: HashSet::new(),
|
expose_hdrs: HashSet::new(),
|
||||||
|
resources: Vec::new(),
|
||||||
|
app: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create CorsBuilder for a specified application.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{http, App, HttpResponse};
|
||||||
|
/// use actix_web::middleware::cors::Cors;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new()
|
||||||
|
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
|
||||||
|
/// .allowed_origin("https://www.rust-lang.org/")
|
||||||
|
/// .resource("/resource", |r| { // register resource
|
||||||
|
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||||
|
/// })
|
||||||
|
/// .register() // construct CORS and return application instance
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
|
||||||
|
CorsBuilder {
|
||||||
|
cors: Some(Inner {
|
||||||
|
origins: AllOrSome::All,
|
||||||
|
origins_str: None,
|
||||||
|
methods: HashSet::new(),
|
||||||
|
headers: AllOrSome::All,
|
||||||
|
expose_hdrs: None,
|
||||||
|
max_age: None,
|
||||||
|
preflight: true,
|
||||||
|
send_wildcard: false,
|
||||||
|
supports_credentials: false,
|
||||||
|
vary_header: true,
|
||||||
|
}),
|
||||||
|
methods: false,
|
||||||
|
error: None,
|
||||||
|
expose_hdrs: HashSet::new(),
|
||||||
|
resources: Vec::new(),
|
||||||
|
app: Some(app),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,11 +444,13 @@ impl<S> Middleware<S> for Cors {
|
|||||||
/// .finish();
|
/// .finish();
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct CorsBuilder {
|
pub struct CorsBuilder<S=()> {
|
||||||
cors: Option<Inner>,
|
cors: Option<Inner>,
|
||||||
methods: bool,
|
methods: bool,
|
||||||
error: Option<http::Error>,
|
error: Option<http::Error>,
|
||||||
expose_hdrs: HashSet<HeaderName>,
|
expose_hdrs: HashSet<HeaderName>,
|
||||||
|
resources: Vec<(String, ResourceHandler<S>)>,
|
||||||
|
app: Option<App<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cors<'a>(parts: &'a mut Option<Inner>, err: &Option<http::Error>)
|
fn cors<'a>(parts: &'a mut Option<Inner>, err: &Option<http::Error>)
|
||||||
@ -417,7 +462,7 @@ fn cors<'a>(parts: &'a mut Option<Inner>, err: &Option<http::Error>)
|
|||||||
parts.as_mut()
|
parts.as_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CorsBuilder {
|
impl<S: 'static> CorsBuilder<S> {
|
||||||
|
|
||||||
/// Add an origin that are allowed to make requests.
|
/// Add an origin that are allowed to make requests.
|
||||||
/// Will be verified against the `Origin` request header.
|
/// Will be verified against the `Origin` request header.
|
||||||
@ -435,7 +480,7 @@ impl CorsBuilder {
|
|||||||
/// Defaults to `All`.
|
/// Defaults to `All`.
|
||||||
///
|
///
|
||||||
/// Builder panics if supplied origin is not valid uri.
|
/// Builder panics if supplied origin is not valid uri.
|
||||||
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder {
|
pub fn allowed_origin(&mut self, origin: &str) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
match Uri::try_from(origin) {
|
match Uri::try_from(origin) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@ -461,7 +506,7 @@ impl CorsBuilder {
|
|||||||
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
///
|
///
|
||||||
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
|
/// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
|
||||||
pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder
|
pub fn allowed_methods<U, M>(&mut self, methods: U) -> &mut CorsBuilder<S>
|
||||||
where U: IntoIterator<Item=M>, Method: HttpTryFrom<M>
|
where U: IntoIterator<Item=M>, Method: HttpTryFrom<M>
|
||||||
{
|
{
|
||||||
self.methods = true;
|
self.methods = true;
|
||||||
@ -482,7 +527,7 @@ impl CorsBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set an allowed header
|
/// Set an allowed header
|
||||||
pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder
|
pub fn allowed_header<H>(&mut self, header: H) -> &mut CorsBuilder<S>
|
||||||
where HeaderName: HttpTryFrom<H>
|
where HeaderName: HttpTryFrom<H>
|
||||||
{
|
{
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
@ -511,7 +556,7 @@ impl CorsBuilder {
|
|||||||
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
///
|
///
|
||||||
/// Defaults to `All`.
|
/// Defaults to `All`.
|
||||||
pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder
|
pub fn allowed_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
|
||||||
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
||||||
{
|
{
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
@ -542,7 +587,7 @@ impl CorsBuilder {
|
|||||||
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
///
|
///
|
||||||
/// This defaults to an empty set.
|
/// This defaults to an empty set.
|
||||||
pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder
|
pub fn expose_headers<U, H>(&mut self, headers: U) -> &mut CorsBuilder<S>
|
||||||
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
where U: IntoIterator<Item=H>, HeaderName: HttpTryFrom<H>
|
||||||
{
|
{
|
||||||
for h in headers {
|
for h in headers {
|
||||||
@ -563,7 +608,7 @@ impl CorsBuilder {
|
|||||||
/// This value is set as the `Access-Control-Max-Age` header.
|
/// This value is set as the `Access-Control-Max-Age` header.
|
||||||
///
|
///
|
||||||
/// This defaults to `None` (unset).
|
/// This defaults to `None` (unset).
|
||||||
pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder {
|
pub fn max_age(&mut self, max_age: usize) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
cors.max_age = Some(max_age)
|
cors.max_age = Some(max_age)
|
||||||
}
|
}
|
||||||
@ -584,7 +629,7 @@ impl CorsBuilder {
|
|||||||
/// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime.
|
/// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime.
|
||||||
///
|
///
|
||||||
/// Defaults to `false`.
|
/// Defaults to `false`.
|
||||||
pub fn send_wildcard(&mut self) -> &mut CorsBuilder {
|
pub fn send_wildcard(&mut self) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
cors.send_wildcard = true
|
cors.send_wildcard = true
|
||||||
}
|
}
|
||||||
@ -603,7 +648,7 @@ impl CorsBuilder {
|
|||||||
///
|
///
|
||||||
/// Builder panics if credentials are allowed, but the Origin is set to "*".
|
/// Builder panics if credentials are allowed, but the Origin is set to "*".
|
||||||
/// This is not allowed by W3C
|
/// This is not allowed by W3C
|
||||||
pub fn supports_credentials(&mut self) -> &mut CorsBuilder {
|
pub fn supports_credentials(&mut self) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
cors.supports_credentials = true
|
cors.supports_credentials = true
|
||||||
}
|
}
|
||||||
@ -621,7 +666,7 @@ impl CorsBuilder {
|
|||||||
/// caches that the CORS headers are dynamic, and cannot be cached.
|
/// caches that the CORS headers are dynamic, and cannot be cached.
|
||||||
///
|
///
|
||||||
/// By default `vary` header support is enabled.
|
/// By default `vary` header support is enabled.
|
||||||
pub fn disable_vary_header(&mut self) -> &mut CorsBuilder {
|
pub fn disable_vary_header(&mut self) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
cors.vary_header = false
|
cors.vary_header = false
|
||||||
}
|
}
|
||||||
@ -634,17 +679,53 @@ impl CorsBuilder {
|
|||||||
/// This is useful application level middleware.
|
/// This is useful application level middleware.
|
||||||
///
|
///
|
||||||
/// By default *preflight* support is enabled.
|
/// By default *preflight* support is enabled.
|
||||||
pub fn disable_preflight(&mut self) -> &mut CorsBuilder {
|
pub fn disable_preflight(&mut self) -> &mut CorsBuilder<S> {
|
||||||
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
cors.preflight = false
|
cors.preflight = false
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finishes building and returns the built `Cors` instance.
|
/// Configure resource for a specific path.
|
||||||
///
|
///
|
||||||
/// This method panics in case of any configuration error.
|
/// This is similar to a `App::resource()` method. Except, cors middleware
|
||||||
pub fn finish(&mut self) -> Cors {
|
/// get registered for the resource.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{http, App, HttpResponse};
|
||||||
|
/// use actix_web::middleware::cors::Cors;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new()
|
||||||
|
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
|
||||||
|
/// .allowed_origin("https://www.rust-lang.org/")
|
||||||
|
/// .allowed_methods(vec!["GET", "POST"])
|
||||||
|
/// .allowed_header(http::header::CONTENT_TYPE)
|
||||||
|
/// .max_age(3600)
|
||||||
|
/// .resource("/resource1", |r| { // register resource
|
||||||
|
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
||||||
|
/// })
|
||||||
|
/// .resource("/resource2", |r| { // register another resource
|
||||||
|
/// r.method(http::Method::HEAD)
|
||||||
|
/// .f(|_| HttpResponse::MethodNotAllowed());
|
||||||
|
/// })
|
||||||
|
/// .register() // construct CORS and return application instance
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
|
||||||
|
where F: FnOnce(&mut ResourceHandler<S>) -> R + 'static
|
||||||
|
{
|
||||||
|
// add resource handler
|
||||||
|
let mut handler = ResourceHandler::default();
|
||||||
|
f(&mut handler);
|
||||||
|
|
||||||
|
self.resources.push((path.to_owned(), handler));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct(&mut self) -> Cors {
|
||||||
if !self.methods {
|
if !self.methods {
|
||||||
self.allowed_methods(vec![Method::GET, Method::HEAD,
|
self.allowed_methods(vec![Method::GET, Method::HEAD,
|
||||||
Method::POST, Method::OPTIONS, Method::PUT,
|
Method::POST, Method::OPTIONS, Method::PUT,
|
||||||
@ -673,6 +754,39 @@ impl CorsBuilder {
|
|||||||
}
|
}
|
||||||
Cors{inner: Rc::new(cors)}
|
Cors{inner: Rc::new(cors)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finishes building and returns the built `Cors` instance.
|
||||||
|
///
|
||||||
|
/// This method panics in case of any configuration error.
|
||||||
|
pub fn finish(&mut self) -> Cors {
|
||||||
|
if !self.resources.is_empty() {
|
||||||
|
panic!("CorsBuilder::resource() was used,
|
||||||
|
to construct CORS `.register(app)` method should be used");
|
||||||
|
}
|
||||||
|
self.construct()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes building Cors middleware and register middleware for application
|
||||||
|
///
|
||||||
|
/// This method panics in case of any configuration error or if non of
|
||||||
|
/// resources are registered.
|
||||||
|
pub fn register(&mut self) -> App<S> {
|
||||||
|
if self.resources.is_empty() {
|
||||||
|
panic!("No resources are registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cors = self.construct();
|
||||||
|
let mut app = self.app.take().expect(
|
||||||
|
"CorsBuilder has to be constructed with Cors::for_app(app)");
|
||||||
|
|
||||||
|
// register resources
|
||||||
|
for (path, mut resource) in self.resources.drain(..) {
|
||||||
|
cors.clone().register(&mut resource);
|
||||||
|
app.register_resource(&path, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
app
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -713,6 +827,23 @@ mod tests {
|
|||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "No resources are registered")]
|
||||||
|
fn no_resource() {
|
||||||
|
Cors::build()
|
||||||
|
.supports_credentials()
|
||||||
|
.send_wildcard()
|
||||||
|
.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Cors::for_app(app)")]
|
||||||
|
fn no_resource2() {
|
||||||
|
Cors::build()
|
||||||
|
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.register();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_origin_allows_all_origins() {
|
fn validate_origin_allows_all_origins() {
|
||||||
let cors = Cors::default();
|
let cors = Cors::default();
|
||||||
@ -866,4 +997,22 @@ mod tests {
|
|||||||
&b"https://www.example.com"[..],
|
&b"https://www.example.com"[..],
|
||||||
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cors_resource() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.configure(
|
||||||
|
|app| Cors::for_app(app)
|
||||||
|
.allowed_origin("https://www.example.com")
|
||||||
|
.resource("/test", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
|
.register())
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
|
||||||
|
// TODO: proper test
|
||||||
|
//assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
assert!(resp.as_response().is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user