Git Product home page Git Product logo

Comments (31)

nicholasday avatar nicholasday commented on May 3, 2024 57

Just for future reference, here is the fairing I wrote for easy CORS:

use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Header, ContentType, Method};
use std::io::Cursor;

pub struct CORS();

impl Fairing for CORS {
    fn info(&self) -> Info {
        Info {
            name: "Add CORS headers to requests",
            kind: Kind::Response
        }
    }

    fn on_response(&self, request: &Request, response: &mut Response) {
        if request.method() == Method::Options || response.content_type() == Some(ContentType::JSON) {
            response.set_header(Header::new("Access-Control-Allow-Origin", "http://localhost:9000"));
            response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, OPTIONS"));
            response.set_header(Header::new("Access-Control-Allow-Headers", "Content-Type"));
            response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
        }

        if request.method() == Method::Options {
            response.set_header(ContentType::Plain);
            response.set_sized_body(Cursor::new(""));
        }
    }
}

You just have to attach it like this:

rocket::ignite()
    .attach(CORS())

from rocket.

SergioBenitez avatar SergioBenitez commented on May 3, 2024 23

@kybishop's got the right idea, but you should rarely, if ever, be constructing a Response object in your handler. Instead, create a new type and implement Responder for it. You can then return it directly from your handler. See the wrapping Responder section of the guide for more info.

For this problem in particular, I would create a new CORS type and implement Responder for it. I implemented a small one, and I copy the code below. It's incomplete, inefficient, and could be made less error-prone, but it works, and using it is clean.

use std::collections::HashSet;
use rocket::response::{self, Response, Responder};
use rocket::http::Method;

struct CORS<R> {
    responder: R,
    allow_origin: &'static str,
    expose_headers: HashSet<&'static str>,
    allow_credentials: bool,
    allow_headers: HashSet<&'static str>,
    allow_methods: HashSet<Method>,
    max_age: Option<usize>
}

type PreflightCORS = CORS<()>;

impl PreflightCORS {
    pub fn preflight(origin: &'static str) -> PreflightCORS {
        CORS::origin((), origin)
    }
}

impl<'r, R: Responder<'r>> CORS<R> {
    pub fn origin(responder: R, origin: &'static str) -> CORS<R> {
        CORS {
            responder: responder,
            allow_origin: origin,
            expose_headers: HashSet::new(),
            allow_credentials: false,
            allow_headers: HashSet::new(),
            allow_methods: HashSet::new(),
            max_age: None
        }
    }

    pub fn any(responder: R) -> CORS<R> {
        CORS::origin(responder, "*")
    }

    pub fn credentials(mut self, value: bool) -> CORS<R> {
        self.allow_credentials = value;
        self
    }

    pub fn methods(mut self, methods: Vec<Method>) -> CORS<R> {
        for method in methods {
            self.allow_methods.insert(method);
        }

        self
    }

    pub fn headers(mut self, headers: Vec<&'static str>) -> CORS<R> {
        for header in headers {
            self.allow_headers.insert(header);
        }

        self
    }

    // TODO: Add more builder methods to set the rest of the fields.
}

impl<'r, R: Responder<'r>> Responder<'r> for CORS<R> {
    fn respond(self) -> response::Result<'r> {
        let mut response = Response::build_from(self.responder.respond()?)
            .raw_header("Access-Control-Allow-Origin", self.allow_origin)
            .finalize();

        match self.allow_credentials {
            true => response.set_raw_header("Access-Control-Allow-Credentials", "true"),
            false => response.set_raw_header("Access-Control-Allow-Credentials", "false")
        };

        if !self.allow_methods.is_empty() {
            let mut methods = String::with_capacity(self.allow_methods.len() * 7);
            for (i, method) in self.allow_methods.iter().enumerate() {
                if i != 0 { methods.push_str(", ") }
                methods.push_str(method.as_str());
            }

            response.set_raw_header("Access-Control-Allow-Methods", methods);
        }

        // FIXME: Get rid of this dupe.
        if !self.allow_headers.is_empty() {
            let mut headers = String::with_capacity(self.allow_headers.len() * 15);
            for (i, header) in self.allow_headers.iter().enumerate() {
                if i != 0 { headers.push_str(", ") }
                headers.push_str(header);
            }

            response.set_raw_header("Access-Control-Allow-Headers", headers);
        }

        // TODO: Inspect and set the rest of the fields.

        Ok(response)
    }

}

You would then use it in routes like:

#[route(OPTIONS, "/item")]
fn cors_preflight() -> PreflightCORS {
    CORS::preflight("http://host.tld")
        .methods(vec![Method::Options, Method::Post])
        .headers(vec!["Content-Type"])
}

#[post("/item")]
fn cors_demo() -> CORS<&'static str> {
    CORS::any("This is the response.")
}

In the particular POST route above, you could do:

#[post("/item")]
fn cors_demo() -> CORS<Option<JSON<Wrapper>>> {
    CORS::any(option_json_wrapper)
}

from rocket.

SergioBenitez avatar SergioBenitez commented on May 3, 2024 8

Now that #55 has landed, we can add proper support for CORS to Rocket! I'm happy to field any design ideas and/or mentor anyone who might be willing to implement some CORS related fairings and structures for contrib.

from rocket.

lawliet89 avatar lawliet89 commented on May 3, 2024 6

I have implemented CORS both as Fairing and for ad-hoc route based usage in a rocket_cors crate. Hopefully the documentation is sufficient. The options are configurable.

The problem with @nicholasday's implementation is that if the routes have any side effects, it will be too late to reverse them if the CORS check fails in on_response.

from rocket.

fabricedesre avatar fabricedesre commented on May 3, 2024 4

Having a "native" built in support would be better. We did something for iron, which was quite simple (https://github.com/fxbox/iron-cors/).

from rocket.

sebasmagri avatar sebasmagri commented on May 3, 2024 2

This is what I'm currently doing to handle the preflight request:

#[route(OPTIONS, "/endpoint/")]
fn options_handler<'a>() -> Response<'a> {
    Response::build()
        .raw_header("Access-Control-Allow-Origin", "http://host.tld")
        .raw_header("Access-Control-Allow-Methods", "OPTIONS, POST")
        .raw_header("Access-Control-Allow-Headers", "Content-Type")
        .finalize()
}

However, for a handler returning data, I can't still find a way to wrap the existing response in a Response to add the headers. The existing handler looks like this:

#[post("/endpoint/", format = "application/json", data = "<config>")]
fn post_handler(config: JSON<Config>) -> Option<JSON<Wrapper>> {
    let wrapper = factory(report_id);
    match wrapper {
        Some(f) => Some(JSON(f(&(config.unwrap())))),
        None => None
    }
}

Is this a correct approach? How could I add headers to the JSON response?

from rocket.

Arzte avatar Arzte commented on May 3, 2024 2

This could be a fun way to use fairings for the first time & contribute to rocket something that would be super helpful for a project of mine. I'm going to get started trying to implement this into contrib via. fairings. @SergioBenitez I'll take some of the code you provided for a CORS type to make this come in faster. (I realize some work is to be done on that example, just part of the fun) I'll send in a pr today or tomorrow with a basic implementation for it.

EDIT: I'm waiting on 0.3.0 as there's more work to be done on fairings, I'm still working on it, but I'm planning to really get a pr in after 0.3 is out.

from rocket.

SergioBenitez avatar SergioBenitez commented on May 3, 2024 2

@SirDoctors Just a heads up: the fairings implementation that just landed in master is very likely to be the version that ships in 0.3.

from rocket.

derekdreery avatar derekdreery commented on May 3, 2024 2

@LeviSchuck you may be interested in https://crates.io/crates/rocket_cors

from rocket.

superjose avatar superjose commented on May 3, 2024 2

Has anyone worked this with async and v 0.5.0-dev (as of this writing)?

I tried implementing the same mechanism suggested by @nicholasday but I wasn't able to do it.

Here's my Fair:

#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;

mod mongo;
use rocket::http::{ContentType, Header, Method};
use rocket::{Request, Response};
use std::io::Cursor;
use std::str::FromStr;

use rocket::fairing::{Fairing, Info, Kind};
use rocket_cors::{AllowedMethods, AllowedOrigins, CorsOptions};

pub struct CORS;

#[rocket::async_trait]
impl Fairing for CORS {
    fn info(&self) -> Info {
        Info {
            name: "Add CORS headers to responses",
            kind: Kind::Response,
        }
    }

    async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
        println!("Setting access control allow origin");
        response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
        response.set_header(Header::new(
            "Access-Control-Allow-Methods",
            "POST, GET, PATCH, OPTIONS",
        ));
        response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
        response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
  
    }
}

#[get("/")]
async fn index() -> &'static str {
    "Hello, world!"
}

#[post("/test")]
async fn testing() -> &'static str {
    let r = mongo::create().await;
    return "Bibibiii";
}
/**
 * This is the Rocket file
 */

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(CORS)
        .mount("/", routes![index, testing])
}

I'm still getting hit by:

Access to fetch at 'http://localhost:8000/test' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

image

I even opened a Stack Overflow question:
https://stackoverflow.com/questions/67478789/how-do-i-enable-cors-in-rust-rocket-v-0-5?noredirect=1#comment119270650_67478789

This helped me do it when I was using sync:
https://stackoverflow.com/questions/62412361/how-to-set-up-cors-or-options-for-rocket-rs/64904947?noredirect=1#comment119272533_64904947

I even tried using the master branch version of rocket_cors but it didn't work.

Just for reference, here's my cargo.toml


[dependencies]
rocket = "0.5.0-dev"
rocket_cors = "0.5.2"
mongodb = "2.0.0-alpha.1"


[dependencies.rocket_contrib]
version = "0.5.0-dev"
default-features = false
features = ["diesel_postgres_pool", "json"]

[patch.crates-io]
rocket = { git = 'https://github.com/SergioBenitez/Rocket', branch = "master" }
rocket_contrib = { git = 'https://github.com/SergioBenitez/Rocket', branch = "master" }
rocket_cors = { git = "https:

from rocket.

hwoodiwiss avatar hwoodiwiss commented on May 3, 2024 2

@superjose I know this is quite old, but I think the problem you're having is because you don't have an #[options("<ENDPOINT>")] for each endpoint, meaning your options requests are receiving a non-successful response, 404.
The browser then likely ignores any CORS headers in the non-successful response.

from rocket.

dlight avatar dlight commented on May 3, 2024 1

@nicholasday perhaps this fairing could be added to contrib?

from rocket.

shaoxp avatar shaoxp commented on May 3, 2024 1

I met exact the same problem. the options request got a 404 response. i am still not clear how to add OPTIONS support.
it seems someone said rocket_cors may help, but i seems can not use it with 0.5-rc.

from rocket.

MCOfficer avatar MCOfficer commented on May 3, 2024 1

I met exact the same problem. the options request got a 404 response. i am still not clear how to add OPTIONS support. it seems someone said rocket_cors may help, but i seems can not use it with 0.5-rc.

You need to use their git branch for 0.5-rc, see #1701

from rocket.

kybishop avatar kybishop commented on May 3, 2024

Info a little more readable than the w3 spec πŸ˜‰: http://enable-cors.org/server.html

from rocket.

SergioBenitez avatar SergioBenitez commented on May 3, 2024

I agree that Rocket should have a nicer way to handle this, and it's on my list of things to determine how to do better. For now, I've added the ability to use #[route(OPTIONS, "/", ...)] to manually handle OPTIONS requests in 2de006d. As soon as #83 is resolved, you'll be able to use options as a decorator directly.

from rocket.

flosse avatar flosse commented on May 3, 2024

I'd love to see a full CORS example :) Did someone already made experiences?

from rocket.

kybishop avatar kybishop commented on May 3, 2024

@sebasmagri JSON impliments the Responder trait. Something like this should work:

#[post("/endpoint/", format = "application/json", data = "<config>")]
fn post_handler<'request>(config: JSON<Config>)
                          -> Option<Result<Response<'request>, &'str>> {
    let wrapper = factory(report_id);
    match wrapper {
        Some(f) => {
            let response = Response::build_from(JSON(f(&(config.unwrap()))).respond().unwrap());

            response.header(hyper::header::AccessControlAllowOrigin::Any)

            Some(response.ok())
        },
        None => None
    }
}

from rocket.

sebasmagri avatar sebasmagri commented on May 3, 2024

I believe having this type into the framework would be really good. Thanks @SergioBenitez

from rocket.

rofrol avatar rofrol commented on May 3, 2024

Regarding the code from #25 (comment), I have found this:

The only valid value for this header is true (case-sensitive). If you don't need credentials, omit this header entirely (rather than setting its value to false).

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

from rocket.

mehcode avatar mehcode commented on May 3, 2024

Now that Rocket v0.3 is here a formalized ( and configurable, see https://echo.labstack.com/middleware/cors for an idea ) should definitely be added to contrib.

from rocket.

skondrashov avatar skondrashov commented on May 3, 2024

I have been using the @nicholasday solution for my project, and it works fine for GET requests. Now I am adding my first POST request and not sure how I can use Rocket for this. Rocket claims success on the request:

POST /image/label/submit application/x-www-form-urlencoded; charset=UTF-8:
    => Matched: POST /image/label/submit (add_label)
    => Outcome: Success
    => Response succeeded.

but Chrome tells a different story:

Failed to load http://localhost:8000/image/label/submit: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:7700' is therefore not allowed access.

Is there a way I can modify that code to work with POST requests? Am I missing something? Not to sound too negative, but I don't think CORS is intuitive or simple enough that everyone who wants to write an API with Rocket should be required to slog through custom solutions like this...

#[post("/image/label/submit", data = "<data>")]
fn add_label(data: String) -> String {
	data
}

This is the API call, in case the mistake is here.

EDIT: Of course, as is standard for asking questions on the internet, I figured it out almost immediately after asking. The problem is that I have the String return type for this POST request, and the fairing only applies to JSON return types. Not that hard to tell if you read the code, but if you can't read like me, my adventure may be helpful! @nicholasday 's solution only works for JSON requests, so if you have issues, make sure your requests return JSON (as I am now going to do), or add a check for ContentType::Plain just like the response.content_type() == Some(ContentType::JSON) in the code.

from rocket.

derekdreery avatar derekdreery commented on May 3, 2024

@tkondrashov I might be wrong but I think that CORS only applies to json.

from rocket.

LeviSchuck avatar LeviSchuck commented on May 3, 2024

Is this considered stalled?

from rocket.

derekdreery avatar derekdreery commented on May 3, 2024

@LeviSchuck I believe this is kinda blocked on fairings being able to return early (because for preflight requests there is no need to go to any route handler - we can just intercept the request early and return whatever parameters are set for Access-Control-Allow-Origin et. al.)

from rocket.

DanielJoyce avatar DanielJoyce commented on May 3, 2024

options requests need to be handle in the routing layer somewhere, where it checks a route matches but doesn't invoke the handler. This way there is no need to duplicate handlers just to return CORS headers.

from rocket.

superjose avatar superjose commented on May 3, 2024

Ooohhh I see @hwoodiwiss !!! Thank you very much!!! What I ended up doing was rerouting my front-end so it uses a proxy instead. Thanks for the help!!!

from rocket.

ta32 avatar ta32 commented on May 3, 2024

@superjose
I keep getting the preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' but when I look at the network response from the rocket server that header is present in other responses for different requests. Are the preflight requests different?

from rocket.

owlmafia avatar owlmafia commented on May 3, 2024

What is the current purpose of this issue? Will rocket support CORS, or is it intended to continue using external projects like rocket_cors?

from rocket.

wadafacc avatar wadafacc commented on May 3, 2024

@ta32 me too, and i don't really know what to do. Anyone help?

from rocket.

Saplyn avatar Saplyn commented on May 3, 2024

Since fairings cannot directly respond to any request, this means that every request have to go through the "routing process" to find a matched route handler (and if failed to find one rocket will respond with 404 in v5.0). So maybe it would be a good idea if we can do something like (if we don't want to enable CORS globally through fairing):

specify cors settings in the router annotation and generates a corresponding OPTIONS route handler...

#[get("/path", cors = /* some cors settings */)]
pub fn handler() -> _ {
    // ...
}

/*
// rocket generated endpoint
#[options("/path")]
pub fn generated() -> _ {
    // ...
}
*/

or have a dedicated cors annotation, which generates a corresponding OPTIONS route handler.

#[cors(/* some cors settings */)]
#[get("/path"]
pub fn handler() -> _ {
    // ...
}

/*
// rocket generated endpoint
#[options("/path")]
pub fn generated() -> _ {
    // ...
}
*/

CORS's OPTIONS preflight doesn't necessarily need to go through that "routing process". So maybe have something being able to answer a request directly would be a solution (if we want to enable CORS globally). However, this may go against rocket's initial design.

#[launch]
fn rocket() -> _ {
    rocket::build()
        // something that shields the entire server,
        // answering preflight requests, or maybe 
        // being able to do more things.
        .equip(cors_preflight_answerer)  // <- this method doesn't actually exist
        .mount("/", routes![/* ... */])
}

Currently the solution would be rocket_cors's catch-all route handler combined with their CORS fairing)

(hope my thought would be helpful and sorry if it doesn't or offends somebody)

from rocket.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.