Reputation: 1169
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?
Upvotes: 22
Views: 14793
Reputation: 589
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.
Upvotes: 2
Reputation: 171
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"));
}
}
Upvotes: 17
Reputation: 13518
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.
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"));
}
}
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
Upvotes: 41
Reputation: 11077
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.
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.
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"
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.
Upvotes: 1
Reputation: 2509
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.
If you on search Rocket's documentation for version 0.5.x.
Trait implemented by fairings: Rocket’s structured middleware.
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],
)
}
Refer to Ibraheem Ahmed answer
Upvotes: 2
Reputation: 425
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"));
}
}
Upvotes: 2
Reputation: 1624
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])
}
Upvotes: 9
Reputation: 1169
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
Upvotes: 3