Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cors for setting CORS headers #112

Merged
merged 19 commits into from
Nov 13, 2021
Merged

Add Cors for setting CORS headers #112

merged 19 commits into from
Nov 13, 2021

Conversation

davidpdrsn
Copy link
Member

Example usage:

use http::{Request, Response, Method, header};
use hyper::Body;
use tower::{ServiceBuilder, ServiceExt, Service};
use tower_http::cors::{CorsLayer, Any};
use std::convert::Infallible;

async fn handle(request: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::empty()))
}

let cors = CorsLayer::new()
    .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS])
    .allow_origin(Any)
    .allow_credentials(false);

let mut service = ServiceBuilder::new()
    .layer(cors)
    .service_fn(handle);

let request = Request::builder()
    .header(header::ORIGIN, "https://example.com")
    .body(Body::empty())
    .unwrap();

let response = service
    .ready()
    .await?
    .call(request)
    .await?;

assert_eq!(
    response.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap(),
    "*",
);

Implementation is based on CorsMiddleware from tide.

@davidpdrsn davidpdrsn added the A-new-middleware Area: new middleware proposals label Jul 16, 2021
@jplatte
Copy link
Collaborator

jplatte commented Aug 2, 2021

I've had a look and one thing I'm missing is a little more flexibility for origins. actix_cors allows you to simply provide a closure to check origins which aren't in the allow list. In the crate docs this is part of their example:

.allowed_origin_fn(|origin, _req_head| {
  origin.as_bytes().ends_with(b".rust-lang.org")
})

and I've previously done a very similar thing with that crate, with an additional origin.starts_with(b"https://") check.

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had a look and one thing I'm missing is a little more flexibility for origins. actix_cors allows you to simply provide a closure to check origins which aren't in the allow list. In the crate docs this is part of their example:

.allowed_origin_fn(|origin, _req_head| {
  origin.as_bytes().ends_with(b".rust-lang.org")
})

and I've previously done a very similar thing with that crate, with an additional origin.starts_with(b"https://") check.

Hmm, yeah, I wonder if what we want is a trait for allowed origin predicates that can be implemented by closures and by T: Into<AnyOr<Origin>>?

examples/tonic-key-value-store/Cargo.toml Outdated Show resolved Hide resolved
tower-http/CHANGELOG.md Outdated Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
@hawkw
Copy link
Member

hawkw commented Aug 2, 2021

I wonder if it would make sense to implement the CORS logic as an AsyncPredicate for use with tower::filter. The CORS layer could then build filter services with that predicate.

OTTOH, I'm not sure how much value there is from this — most of the meat is in the Future impl, which would stay the same either way.

@davidpdrsn
Copy link
Member Author

Hmm, yeah, I wonder if what we want is a trait for allowed origin predicates that can be implemented by closures and by T: Into<AnyOr>?

I like that idea! I'll experiment.

@davidpdrsn
Copy link
Member Author

Another thing to consider is that since parts of this code is copied from tide, we have to include their license somewhere.

Copy link

@nkconnor nkconnor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding my comments from Discord so I can add follow-ups given the time

tower-http/src/cors.rs Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Show resolved Hide resolved
@fourbytes
Copy link

Using Axum for a project at the moment and in need of a CORS layer, any idea when this will be ready?

tower-http/src/cors.rs Outdated Show resolved Hide resolved
@davidpdrsn
Copy link
Member Author

Using Axum for a project at the moment and in need of a CORS layer, any idea when this will be ready?

I'm gonna look into this once axum 0.2 is released, which will happen this week. I can't give an ETA on merging this however. Until then you can apply the CORS headers manually and add a catch-all route for OPTIONS requests.

@fourbytes
Copy link

Ended up quickly forking this branch as I needed the ability to allow multiple origins which was difficult to do with manually injected headers.

With these changes it seems to be working nicely.

@davidpdrsn
Copy link
Member Author

davidpdrsn commented Aug 30, 2021

Believe I've addresses all the comments now. Thanks for the feedback!

Allow-Origin can be now determined via a closure, similar to actix-cors:

use tower_http::cors::{CorsLayer, Any};
use http::{HeaderValue, request::Parts};

let layer = CorsLayer::new().allow_origin(|origin: &HeaderValue, _request_head: &Parts| {
    origin.as_bytes().ends_with(b".rust-lang.org")
});

@jplatte @hawkw @fourbytes @nkconnor Wanna give this another look?

@nkconnor
Copy link

nkconnor commented Aug 30, 2021

lg2m - thanks!

Copy link

@fourbytes fourbytes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem to be working for me yet due to an issue with the valid method check. Other than that though, API looks great.

tower-http/src/cors.rs Outdated Show resolved Hide resolved
@davidpdrsn davidpdrsn requested a review from hawkw August 31, 2021 07:15
Copy link
Collaborator

@jplatte jplatte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing this still doesn't allow which actix-cors allows is

Cors::ctor()
    .allowed_origin("http://127.0.0.1")
    .allowed_origin("foo.bar.org")
    .allowed_origin_fn(|origin, _| {
        let origin = origin.as_bytes();
        origin.starts_with(b"https://") && origin.ends_with(b".rust-lang.org")
    })

which also means that it's impossible with the current code structure for clients to discover some of the origins that would be allowed to use a route, at least when using a closure (it will look like no origins are allowed when doing the request from a disallowed origin). I think what actix-cors does is send all origins that are specified explicitly regardless of whether they match, and additionally send the request origin back in the header if the function marks it as allowed.

I don't think it would be a problem for me, but I thought I'd still bring it up.

tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Show resolved Hide resolved
@davidpdrsn
Copy link
Member Author

which also means that it's impossible with the current code structure for clients to discover some of the origins that would be allowed to use a route, at least when using a closure (it will look like no origins are allowed when doing the request from a disallowed origin). I think what actix-cors does is send all origins that are specified explicitly regardless of whether they match, and additionally send the request origin back in the header if the function marks it as allowed.

Hm thats a good point. I was wondering why actix that it setup the way they did. Although I do think its unfortunate to have to store both a list a allowed origins, and a closure to call if nothing else matches 🤔

I don't think it would be a problem for me, but I thought I'd still bring it up.

Would be an issue for me either. I'm leaning towards keeping it as is 🤷

tower-http/src/cors.rs Outdated Show resolved Hide resolved
Copy link

@DoumanAsh DoumanAsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're interested in having cors for our ongoing migration to axum.
I've put some minor comments

tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
tower-http/src/cors.rs Outdated Show resolved Hide resolved
Copy link

@DoumanAsh DoumanAsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small suggestions to improve efficiency and performance

tower-http/src/cors.rs Show resolved Hide resolved
tower-http/src/cors.rs Show resolved Hide resolved
tower-http/src/cors.rs Show resolved Hide resolved
tower-http/src/cors.rs Show resolved Hide resolved
@silvioprog
Copy link

Hey guys, is there any plan when it will be merged?

@davidpdrsn
Copy link
Member Author

I basically just have to clean up the docs and then it should be good to go. Should happen somewhat soon but dunno exactly when. I recommend going with a git dependency on tower-http for now.

@davidpdrsn davidpdrsn changed the title Add Cors for settings CORS headers Add Cors for setting CORS headers Oct 8, 2021
@davidpdrsn davidpdrsn merged commit c785742 into master Nov 13, 2021
@davidpdrsn davidpdrsn deleted the cors branch November 13, 2021 19:36
davidpdrsn added a commit that referenced this pull request Nov 13, 2021
- New middleware: Add `Cors` for setting [CORS] headers ([#112])
- New middleware: Add `AsyncRequireAuthorization` ([#118])
- `Compression`: Don't recompress HTTP responses ([#140])
- `Compression` and `Decompression`: Pass configuration from layer into middleware ([#132])
- `ServeDir` and `ServeFile`: Improve performance ([#137])
- `Compression`: Remove needless `ResBody::Error: Into<BoxError>` bounds ([#117])
- `ServeDir`: Percent decode path segments ([#129])
- `ServeDir`: Use correct redirection status ([#130])
- `ServeDir`: Return `404 Not Found` on requests to directories if
  `append_index_html_on_directories` is set to `false` ([#122])

[#112]: #112
[#118]: #118
[#140]: #140
[#132]: #132
[#137]: #137
[#117]: #117
[#129]: #129
[#130]: #130
[#122]: #122
@davidpdrsn davidpdrsn mentioned this pull request Nov 13, 2021
davidpdrsn added a commit that referenced this pull request Nov 13, 2021
- New middleware: Add `Cors` for setting [CORS] headers ([#112])
- New middleware: Add `AsyncRequireAuthorization` ([#118])
- `Compression`: Don't recompress HTTP responses ([#140])
- `Compression` and `Decompression`: Pass configuration from layer into middleware ([#132])
- `ServeDir` and `ServeFile`: Improve performance ([#137])
- `Compression`: Remove needless `ResBody::Error: Into<BoxError>` bounds ([#117])
- `ServeDir`: Percent decode path segments ([#129])
- `ServeDir`: Use correct redirection status ([#130])
- `ServeDir`: Return `404 Not Found` on requests to directories if
  `append_index_html_on_directories` is set to `false` ([#122])

[#112]: #112
[#118]: #118
[#140]: #140
[#132]: #132
[#137]: #137
[#117]: #117
[#129]: #129
[#130]: #130
[#122]: #122
davidpdrsn added a commit that referenced this pull request Nov 13, 2021
- New middleware: Add `Cors` for setting [CORS] headers ([#112])
- New middleware: Add `AsyncRequireAuthorization` ([#118])
- `Compression`: Don't recompress HTTP responses ([#140])
- `Compression` and `Decompression`: Pass configuration from layer into middleware ([#132])
- `ServeDir` and `ServeFile`: Improve performance ([#137])
- `Compression`: Remove needless `ResBody::Error: Into<BoxError>` bounds ([#117])
- `ServeDir`: Percent decode path segments ([#129])
- `ServeDir`: Use correct redirection status ([#130])
- `ServeDir`: Return `404 Not Found` on requests to directories if
  `append_index_html_on_directories` is set to `false` ([#122])

[#112]: #112
[#118]: #118
[#140]: #140
[#132]: #132
[#137]: #137
[#117]: #117
[#129]: #129
[#130]: #130
[#122]: #122
davidpdrsn added a commit that referenced this pull request Nov 13, 2021
- New middleware: Add `Cors` for setting [CORS] headers ([#112])
- New middleware: Add `AsyncRequireAuthorization` ([#118])
- `Compression`: Don't recompress HTTP responses ([#140])
- `Compression` and `Decompression`: Pass configuration from layer into middleware ([#132])
- `ServeDir` and `ServeFile`: Improve performance ([#137])
- `Compression`: Remove needless `ResBody::Error: Into<BoxError>` bounds ([#117])
- `ServeDir`: Percent decode path segments ([#129])
- `ServeDir`: Use correct redirection status ([#130])
- `ServeDir`: Return `404 Not Found` on requests to directories if
  `append_index_html_on_directories` is set to `false` ([#122])

[#112]: #112
[#118]: #118
[#140]: #140
[#132]: #132
[#137]: #137
[#117]: #117
[#129]: #129
[#130]: #130
[#122]: #122
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-new-middleware Area: new middleware proposals
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants