mirror of
https://github.com/actix/actix-web.git
synced 2024-12-19 14:49:01 +00:00
add cors preflight request support
This commit is contained in:
parent
ce78f17a79
commit
d7f59ce481
2 changed files with 137 additions and 5 deletions
|
@ -404,6 +404,16 @@ impl HttpResponseBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method calls provided closure with builder reference if value is Some.
|
||||||
|
pub fn if_some<T, F>(&mut self, value: Option<&T>, f: F) -> &mut Self
|
||||||
|
where F: Fn(&T, &mut HttpResponseBuilder) + 'static
|
||||||
|
{
|
||||||
|
if let Some(val) = value {
|
||||||
|
f(val, self);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a body and generate `HttpResponse`.
|
/// Set a body and generate `HttpResponse`.
|
||||||
///
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
|
|
|
@ -1,5 +1,48 @@
|
||||||
//! Cross-origin resource sharing (CORS) for Actix applications
|
//! Cross-origin resource sharing (CORS) for Actix applications
|
||||||
|
//!
|
||||||
|
//! CORS middleware could be used with application and with resource.
|
||||||
|
//! First you need to construct CORS middleware instance.
|
||||||
|
//!
|
||||||
|
//! To construct a cors:
|
||||||
|
//!
|
||||||
|
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
||||||
|
//! 2. Use any of the builder methods to set fields in the backend.
|
||||||
|
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
|
||||||
|
//!
|
||||||
|
//! This constructed middleware could be used as parameter for `Application::middleware()` or
|
||||||
|
//! `Resource::middleware()` methods.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate http;
|
||||||
|
//! # extern crate actix_web;
|
||||||
|
//! use http::header;
|
||||||
|
//! use actix_web::middleware::cors;
|
||||||
|
//!
|
||||||
|
//! fn index(mut req: HttpRequest) -> &'static str {
|
||||||
|
//! "Hello world"
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let app = Application::new()
|
||||||
|
//! .resource("/index.html", |r| {
|
||||||
|
//! r.middleware(cors::Cors::build() // <- Register CORS middleware
|
||||||
|
//! .allowed_origin("https://www.rust-lang.org/")
|
||||||
|
//! .allowed_methods(vec!["GET", "POST"])
|
||||||
|
//! .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||||
|
//! .allowed_header(header::CONTENT_TYPE)
|
||||||
|
//! .max_age(3600)
|
||||||
|
//! .finish().expect("Can not create CORS middleware"))
|
||||||
|
//! r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||||
|
//! r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
//! })
|
||||||
|
//! .finish();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//! In this example custom *CORS* middleware get registered for "/index.html" endpoint.
|
||||||
|
//!
|
||||||
|
//! Cors middleware automatically handle *OPTIONS* preflight request.
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use http::{self, Method, HttpTryFrom, Uri};
|
use http::{self, Method, HttpTryFrom, Uri};
|
||||||
|
@ -9,7 +52,7 @@ use error::{Result, ResponseError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middleware::{Middleware, Response, Started};
|
use middleware::{Middleware, Response, Started};
|
||||||
use httpcodes::HTTPBadRequest;
|
use httpcodes::{HTTPOk, HTTPBadRequest};
|
||||||
|
|
||||||
/// A set of errors that can occur during processing CORS
|
/// A set of errors that can occur during processing CORS
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail)]
|
||||||
|
@ -26,6 +69,9 @@ pub enum Error {
|
||||||
/// The request header `Access-Control-Request-Method` has an invalid value
|
/// The request header `Access-Control-Request-Method` has an invalid value
|
||||||
#[fail(display="The request header `Access-Control-Request-Method` has an invalid value")]
|
#[fail(display="The request header `Access-Control-Request-Method` has an invalid value")]
|
||||||
BadRequestMethod,
|
BadRequestMethod,
|
||||||
|
/// The request header `Access-Control-Request-Headers` has an invalid value
|
||||||
|
#[fail(display="The request header `Access-Control-Request-Headers` has an invalid value")]
|
||||||
|
BadRequestHeaders,
|
||||||
/// The request header `Access-Control-Request-Headers` is required but is missing.
|
/// The request header `Access-Control-Request-Headers` is required but is missing.
|
||||||
#[fail(display="The request header `Access-Control-Request-Headers` is required but is
|
#[fail(display="The request header `Access-Control-Request-Headers` is required but is
|
||||||
missing")]
|
missing")]
|
||||||
|
@ -86,6 +132,14 @@ impl<T> AllOrSome<T> {
|
||||||
pub fn is_some(&self) -> bool {
|
pub fn is_some(&self) -> bool {
|
||||||
!self.is_all()
|
!self.is_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns &T
|
||||||
|
pub fn as_ref(&self) -> Option<&T> {
|
||||||
|
match *self {
|
||||||
|
AllOrSome::All => None,
|
||||||
|
AllOrSome::Some(ref t) => Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Middleware` for Cross-origin resource sharing support
|
/// `Middleware` for Cross-origin resource sharing support
|
||||||
|
@ -97,6 +151,7 @@ pub struct Cors {
|
||||||
origins: AllOrSome<HashSet<Uri>>,
|
origins: AllOrSome<HashSet<Uri>>,
|
||||||
headers: AllOrSome<HashSet<HeaderName>>,
|
headers: AllOrSome<HashSet<HeaderName>>,
|
||||||
max_age: Option<usize>,
|
max_age: Option<usize>,
|
||||||
|
send_wildcards: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cors {
|
impl Cors {
|
||||||
|
@ -107,6 +162,7 @@ impl Cors {
|
||||||
methods: HashSet::new(),
|
methods: HashSet::new(),
|
||||||
headers: AllOrSome::All,
|
headers: AllOrSome::All,
|
||||||
max_age: None,
|
max_age: None,
|
||||||
|
send_wildcards: false,
|
||||||
}),
|
}),
|
||||||
methods: false,
|
methods: false,
|
||||||
error: None,
|
error: None,
|
||||||
|
@ -148,6 +204,33 @@ impl Cors {
|
||||||
Err(Error::MissingRequestMethod)
|
Err(Error::MissingRequestMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_allowed_headers<S>(&self, req: &mut HttpRequest<S>) -> Result<(), Error> {
|
||||||
|
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
|
||||||
|
if let Ok(headers) = hdr.to_str() {
|
||||||
|
match self.headers {
|
||||||
|
AllOrSome::All => return Ok(()),
|
||||||
|
AllOrSome::Some(ref allowed_headers) => {
|
||||||
|
let mut hdrs = HashSet::new();
|
||||||
|
for hdr in headers.split(',') {
|
||||||
|
match HeaderName::try_from(hdr.trim()) {
|
||||||
|
Ok(hdr) => hdrs.insert(hdr),
|
||||||
|
Err(_) => return Err(Error::BadRequestHeaders)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hdrs.is_empty() && !hdrs.is_subset(allowed_headers) {
|
||||||
|
return Err(Error::HeadersNotAllowed)
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::BadRequestHeaders)
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingRequestHeaders)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Middleware<S> for Cors {
|
impl<S> Middleware<S> for Cors {
|
||||||
|
@ -156,11 +239,28 @@ impl<S> Middleware<S> for Cors {
|
||||||
if Method::OPTIONS == *req.method() {
|
if Method::OPTIONS == *req.method() {
|
||||||
self.validate_origin(req)?;
|
self.validate_origin(req)?;
|
||||||
self.validate_allowed_method(req)?;
|
self.validate_allowed_method(req)?;
|
||||||
|
self.validate_allowed_headers(req)?;
|
||||||
|
|
||||||
|
Ok(Started::Response(
|
||||||
|
HTTPOk.build()
|
||||||
|
.if_some(self.max_age.as_ref(), |max_age, res| {
|
||||||
|
let _ = res.header(
|
||||||
|
header::ACCESS_CONTROL_MAX_AGE, format!("{}", max_age).as_str());})
|
||||||
|
.if_some(self.headers.as_ref(), |headers, res| {
|
||||||
|
let _ = res.header(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
|
headers.iter().fold(String::new(), |s, v| s + v.as_str()).as_str());})
|
||||||
|
.header(
|
||||||
|
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||||
|
self.methods.iter().fold(String::new(), |s, v| s + v.as_str()).as_str())
|
||||||
|
.finish()
|
||||||
|
.unwrap()))
|
||||||
|
} else {
|
||||||
|
Ok(Started::Done)
|
||||||
}
|
}
|
||||||
Ok(Started::Done)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&self, _: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
fn response(&self, _: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
||||||
Ok(Response::Done(resp))
|
Ok(Response::Done(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +271,7 @@ impl<S> Middleware<S> for Cors {
|
||||||
///
|
///
|
||||||
/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
||||||
/// 2. Use any of the builder methods to set fields in the backend.
|
/// 2. Use any of the builder methods to set fields in the backend.
|
||||||
/// 3. Call [finish](#method.finish) to retrieve the constructed backend.
|
/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -334,6 +434,28 @@ impl CorsBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a wildcard origins
|
||||||
|
///
|
||||||
|
/// If send widlcard is set and the `allowed_origins` parameter is `All`, a wildcard
|
||||||
|
/// `Access-Control-Allow-Origin` response header is sent, rather than the request’s
|
||||||
|
/// `Origin` header.
|
||||||
|
///
|
||||||
|
/// This is the `supports credentials flag` in the
|
||||||
|
/// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
|
||||||
|
///
|
||||||
|
/// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and
|
||||||
|
/// `allow_credentials` set to `true`. Depending on the mode of usage, this will either result
|
||||||
|
/// in an `Error::CredentialsWithWildcardOrigin` error during actix launch or runtime.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
#[cfg_attr(feature = "serialization", serde(default))]
|
||||||
|
pub fn send_wildcard(&mut self) -> &mut CorsBuilder {
|
||||||
|
if let Some(cors) = cors(&mut self.cors, &self.error) {
|
||||||
|
cors.send_wildcards = true
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Finishes building and returns the built `Cors` instance.
|
/// Finishes building and returns the built `Cors` instance.
|
||||||
pub fn finish(&mut self) -> Result<Cors, http::Error> {
|
pub fn finish(&mut self) -> Result<Cors, http::Error> {
|
||||||
if !self.methods {
|
if !self.methods {
|
||||||
|
|
Loading…
Reference in a new issue