21

I've got a backend running rocket.rs which my flutter web app sends a request to, but it can't get past the OPTIONS response.

I have tried adding CORS (rocket_cors) to the backend and having a options response, but it still sends back:

Error: XMLHttpRequest error.
    dart:sdk_internal 124039:30                           get current
packages/http/src/browser_client.dart.lib.js 214:124  <fn>

I have added the following to my rocket project:

#[options("/")]
fn send_options<'a>(path: PathBuf) -> Response<'a> {
    let mut res = Response::new();
    res.set_status(Status::new(200, "No Content"));
    res.adjoin_header(ContentType::Plain);
    res.adjoin_raw_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
    res.adjoin_raw_header("Access-Control-Allow-Origin", "*");
    res.adjoin_raw_header("Access-Control-Allow-Credentials", "true");
    res.adjoin_raw_header("Access-Control-Allow-Headers", "Content-Type");
    res

And my flutter app is running this request:

Future<String> fetchData() async {
  final data2 = await http.get("http://my-web-site.com").then((response) { // doesn't get past here
    return response.body; 
  });
  return data2;
}

Question: Is this the proper way to respond to OPTION requests, and if not, how can I implement it in rocket.rs?

2

8 Answers 8

41

In order for a server to provide an external API it needs to be able to deal with Cross Origin Resource Sharing (CORS). CORS is an HTTP-header based mechanism that allows a server to indicate which origins (domain, protocol, or port) that a browser should permit loading of resources.

You can create a fairing to handle CORS globally for your app. A very permissive version would be as follows, but of course, you'll have to tailor to your specific application.

Rocket 0.4

use rocket::http::Header;
use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};

pub struct CORS;

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

    fn on_response(&self, request: &Request, response: &mut Response) {
        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"));
    }
}

Rocket 0.5

use rocket::http::Header;
use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};

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>) {
        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"));
    }
}

You just have to attach the fairing like this:

rocket::ignite().attach(CORS)

Alternatively, you can use the rocket_cors crate.

use rocket::http::Method;
use rocket_cors::{AllowedOrigins, CorsOptions};

let cors = CorsOptions::default()
    .allowed_origins(AllowedOrigins::all())
    .allowed_methods(
        vec![Method::Get, Method::Post, Method::Patch]
            .into_iter()
            .map(From::from)
            .collect(),
    )
    .allow_credentials(true);

rocket::ignite().attach(cors.to_cors().unwrap())

You can learn more about CORS and Access Control headers here

6
  • Things change a bit when you start using v 0.5.0 and up of rocket and async rocket.rs/master/guide/fairings
    – Jose A
    Commented May 10, 2021 at 20:46
  • Sure, but 0.5 has not been officially released yet, so I'll update the answer then. Commented May 11, 2021 at 2:21
  • 3
    In 0.5, you'll need to add #[rocket::async_trait] attribute to the implementation and make on_response async with a lifetime.
    – TimY
    Commented Nov 17, 2021 at 9:31
  • you forgot to put <'r> in on_response. It should be on_response<'r> Commented Feb 14, 2022 at 6:31
  • does the wildcard star really work? I think it doesn't and you'll need to do something like let origin = _request.headers().get_one("origin").unwrap(); and let headers = _request.headers().iter().map(|h| h.name.to_string()).collect::<Vec<String>>().join(", "); for origin and headers
    – bersling
    Commented Oct 9, 2022 at 20:57
16

This worked for me with rocket 0.5.0-rc.2 and no other dependencies. It's based on the answers above and some internet searches.

use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Header;
use rocket::log::private::debug;
use rocket::serde::json::Json;
use rocket::{Request, Response};

#[macro_use]
extern crate rocket;

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

/// Some getter
#[get("/")]
fn index() -> &'static str {
    "Hello CORS"
}

/// Some setter
#[post("/", data = "<data>")]
async fn insert(data: Json<Vec<String>>) {
    debug!("Received data");
}

/// Catches all OPTION requests in order to get the CORS related Fairing triggered.
#[options("/<_..>")]
fn all_options() {
    /* Intentionally left empty */
}

pub struct Cors;

#[rocket::async_trait]
impl Fairing for Cors {
    fn info(&self) -> Info {
        Info {
            name: "Cross-Origin-Resource-Sharing Fairing",
            kind: Kind::Response,
        }
    }

    async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
        response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
        response.set_header(Header::new(
            "Access-Control-Allow-Methods",
            "POST, PATCH, PUT, DELETE, HEAD, OPTIONS, GET",
        ));
        response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
        response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
    }
}
2
  • 4
    God bless you man. Commented Aug 9, 2022 at 19:40
  • 1
    Should .attach come before .mount?
    – Dilawar
    Commented Mar 1, 2023 at 6:53
9

This did the trick for me:

use rocket::http::Header;
use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};

pub struct CORS;

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

    async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
        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"));
    }
}

And you have to attach it in the function with the launch macro:

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

Lambda Fairy's comment answered it for me.
I put it all in the GET Handler:

#[get("/")]
fn get_handler<'a>() -> Response<'a> {
    let mut res = Response::new();
    res.set_status(Status::new(200, "No Content"));
    res.adjoin_header(ContentType::Plain);
    res.adjoin_raw_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
    res.adjoin_raw_header("Access-Control-Allow-Origin", "*");
    res.adjoin_raw_header("Access-Control-Allow-Credentials", "true");
    res.adjoin_raw_header("Access-Control-Allow-Headers", "Content-Type");
    res.set_sized_body(Cursor::new("Response")); 
    res
0
2

in case someone is looking for rocket>= rc5.0

use rocket::http::Header;
use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};

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, req: &'r Request<'_>, response: &mut Response<'r>) {
        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"));
    }
}
0
2

To have Cross Origin Resource Sharing support you must intercept responses sent by your Rocket server. You want to implement a middleware to achieve that, on Rocket you must implement the Fairing trait on a struct in order to achieve that.

Rocket v0.5.x (Not stable)

If you on search Rocket's documentation for version 0.5.x.

Trait implemented by fairings: Rocket’s structured middleware.

Source

You must decorate the Fairing trait implementation with the rocket::async-trait attribute.

use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Header;
use rocket::{Request, Response};

pub struct Cors;

#[rocket::async_trait]
impl Fairing for Cors {
    fn info(&self) -> Info {
        Info {
            name: "Cross-Origin-Resource-Sharing Middleware",
            kind: Kind::Response,
        }
    }

    async fn on_response<'r>(&self,
        request: &'r Request<'_>,
        response: &mut Response<'r>) {
        response.set_header(Header::new(
            "access-control-allow-origin",
            "https://example.com",
        ));
        response.set_header(Header::new(
            "access-control-allow-methods",
            "GET, PATCH, OPTIONS",
        ));
    }
}

On your main.rs file, you must attach the middleware:

mod config;
mod middleware;
mod routes;

use self::config::Config;

#[macro_use]
extern crate rocket;

#[launch]
async fn rocket() -> _ {
    let config = Config::new();

    rocket::custom(&config.server_config)
        .attach(middleware::cors::Cors)
        .mount(
            "/api/v1",
            routes![routes::index],
        )
}

Rocket 0.4.x (Stable)

Refer to Ibraheem Ahmed answer

2
  • 1
    No need to add another crate, just use #[rocket::async_trait] as shown in the docs you linked to.
    – Herohtar
    Commented Jan 8, 2022 at 22:06
  • 1
    Good example with putting the CORS related code into a separate middleware module. Commented Dec 28, 2022 at 17:12
1

Jesus. After several hours I finally got it working. First and foremost, a small warning: We are working with highly unstable code and this answer may be obsolete by the time you're reading. The edit from matthewscottgordon in Ibraheem Ahmeed's answer pointed me to the right direction.

We're going to be using the rocket_cors crate.

1. Install an old version of Rust Nightly. As of Jan 22nd, Rust Nightly V 1.6.0 breaks mongodb 2.1.0.

Do this just in case the latest nightly isn't compiling.

You can do this in the following fashion:

rustup override set nightly-2021-11-10 This will download 1.58 nightly.

2. Add the rocket_cors dependency. As of now, the master version has a 1.6.0 release, which targets rocket's 0.5.0-rc.1 release

rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }

This is my cargo.toml:


[dependencies]
rocket = {version ="0.5.0-rc.1", features=["json"]}
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }
reqwest = {version = "0.11.6", features = ["json"] }
serde= {version = "1.0.117", features= ["derive"]}
mongodb = "2.1.0"
rand = "0.8.4"

3. Add the following to your main Rocket function:

extern crate rocket;

use std::error::Error;
use rocket_cors::{AllowedOrigins, CorsOptions};

#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let cors = CorsOptions::default()
        .allowed_origins(AllowedOrigins::all())
        .allowed_methods(
            vec![Method::Get, Method::Post, Method::Patch]
                .into_iter()
                .map(From::from)
                .collect(),
        )
        .allow_credentials(true)
        .to_cors()?;

    // let path = concat!(env!("CARGO_MANIFEST_DIR"), "/public");
    rocket::build()
        .mount("/", routes![index, upload::upload, post_favorites])
        .attach(cors)
        .launch()
        .await?;


    Ok(())
}

If by any chance this answer doesn't work for you, be sure to check out rocket_cors repository for up to date examples. The example above was used with the fairing.rs file

Check out the repo's Cargo.toml file.

To check for the default values in let cors, (if using VS Code, and the Rust extension) hover over CorsOptions with your mouse.

1

I got this working for my service with Rocket v0.5-rc thanks to the answers here and this GitHub comment.

For my use case I had no need to customize the CORS headers for specific requests. If you have more elaborate requirements, use rocket_cors.

Create a CORS Fairing to set the CORS headers on every response.

use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Header, Method, Status};
use rocket::{Request, Response};

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>) {
        if request.method() == Method::Options {
            response.set_status(Status::NoContent);
            response.set_header(Header::new(
                "Access-Control-Allow-Methods",
                "POST, PATCH, GET, DELETE",
            ));
            response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
        }

        response.set_header(Header::new(
            "Access-Control-Allow-Origin",
            "http://localhost:3000",
        ));
        response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
    }
}

Then affix this fairing to your rocket with rocket::build().attach(cors::CORS) and you're all set.

Full credit to the answers above as this was heavily inspired by them. The important part that was missing was setting the NoContent status for all options requests. Thankfully Domenico Gaeni's example included this:

response.set_status(Status::NoContent)

Without this Rocket would 404 on every option request. By setting the status here in the fairing there's no need to define any special routes to serve option requests.

If you're using this note the Access-Control-Allow-Origin header needs to be customized to your use case. I hardcoded it to http://localhost:3000 as I couldn't bring myself to use * in the event someone copy-and-pasted it in full.

In real life you'll likely want to set Access-Control-Allow-Origin based on the request origin header or Rocket.toml config or both so as to support your local development and test/staging/production servers.

1
  • This solution has the caveat that Rocket logs a 404 for each preflight request. It works fine as the browser looks at the response headers on the preflight and ignore the status code (in Safari at least). Still, the server log is annoying. You can suppress the error log by adding this hack after setting the status: let _ = response.body_mut().take();.
    – Dave Teare
    Commented Feb 21, 2023 at 19:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.