如何为Rocket.rs设置CORS或OPTIONS

20
我是一名有用的助手,可以为您翻译文本。

我正在运行 rocket.rs 后端,我的 Flutter Web 应用程序向其发送请求,但无法通过 OPTIONS 响应。

我尝试在后端添加 CORS(rocket_cors)并提供一个 options 响应,但仍然返回:

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

我已经将以下内容添加到我的火箭项目中:

#[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

我的Flutter应用正在运行此请求:

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;
}

问题:这是响应OPTION请求的正确方式吗?如果不是,我该如何在rocket.rs中实现它?


1
对于 GET 请求,浏览器会跳过 OPTIONS 预检并直接执行 GET。因此,您可能需要在 GET 处理程序中包含 CORS 标头。 - Lambda Fairy
@LambdaFairy 那个方法可行!谢谢,如果你想回答的话,我可以标记它为已解决。 - Will
8个回答

37

为了使服务器提供外部API,它需要处理跨域资源共享(CORS)。CORS是一种基于HTTP标头的机制,允许服务器指示浏览器应允许加载哪些源(域名、协议或端口)的资源。

您可以创建一个适用于整个应用程序的fairing来处理CORS。下面是一个非常宽容的版本,但当然,您需要根据您具体的应用程序进行定制。

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

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

你只需要像这样安装舱壳:

rocket::ignite().attach(CORS)

您也可以使用 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())

你可以在这里了解有关CORS和访问控制头的更多信息。


当您开始使用Rocket和Async的v0.5.0及更高版本时,事情会有所改变。请参考https://rocket.rs/master/guide/fairings/。 - Jose A
当然可以,但是0.5版本尚未正式发布,所以我会在那时更新答案。 - Ibraheem Ahmed
3
在0.5版本中,你需要为实现添加#[rocket::async_trait]属性,并将on_response改为具有生命周期的async函数。 - TimY
您忘记在 on_response 中加入<'r>。应该是 on_response<'r> - HumbleCoder
通配符星号真的有效吗?我认为不是,你需要像这样做:let origin = _request.headers().get_one("origin").unwrap();let headers = _request.headers().iter().map(|h| h.name.to_string()).collect::<Vec<String>>().join(", "); 来获取 origin 和 headers。 - bersling
小心。这段代码忽略了所谓的通配符异常 - jub0bs

15

这对我而言有效,使用的是rocket 0.5.0-rc.2版本且没有其他依赖项。它基于上述答案和一些互联网搜索。

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

3
愿上帝保佑你,伙计。 - balanceglove2
.attach 应该放在 .mount 之前吗? - Dilawar

8
这对我很有帮助:
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"));
    }
}

您需要在函数中使用启动宏将其附加:

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

3
Lambda Fairy 的回复已经为我解答了。
我将所有内容都放在GET处理函数中:
#[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

2

如果有人正在寻找火箭版本>= 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"));
    }
}

2
要支持跨域资源共享,您必须拦截由Rocket服务器发送的响应。您需要实现一个中间件来实现这一点,在Rocket上,您必须在一个结构体上实现“Fairing”特征以实现这一点。
Rocket v0.5.x(不稳定版)
如果您正在搜索版本为0.5.x的Rocket文档。
Trait implemented by fairings: Rocket’s structured middleware.

来源

你必须使用rocket::async-trait属性来装饰 Fairing trait 的实现。

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

在你的 main.rs 文件中,必须附加中间件:
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(稳定版)

请参考{{link1:伊布拉欣·艾哈迈德的回答}}


1
不需要添加其他的板条箱,只需按照您链接的文档中所示使用 #[rocket::async_trait] 即可。 - Herohtar
1
将CORS相关代码放入单独的中间件模块是一个很好的示例。 - Milkncookiez

1

耶稣。经过几个小时的努力,我终于让它工作了。首先,一个小警告:我们正在使用高度不稳定的代码,当你阅读时,这个答案可能已经过时了。来自 matthewscottgordon 在 Ibraheem Ahmeed 的回答中的编辑指引了我正确的方向。

我们将使用 rocket_cors crate。

1. 安装旧版本的 Rust Nightly。截至 1 月 22 日,Rust Nightly V 1.6.0 破坏了 mongodb 2.1.0。

为了防止最新的 nightly 无法编译,请执行以下操作:

您可以按照以下方式执行:

rustup override set nightly-2021-11-10 这将下载 1.58 版本的 nightly。

2. 添加 rocket_cors 依赖项。目前,master 版本已发布了 1.6.0 版本,该版本针对 rocket 的 0.5.0-rc.1 发布。

``` rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" } ```
这是我的 `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"

将以下内容添加到您的主 Rocket 函数中:
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(())
}

如果这个答案对你不起作用,一定要查看rocket_cors repository以获取最新的示例。上面的示例是使用fairing.rs文件使用的。
查看存储库的Cargo.toml文件。
要检查let cors中的默认值(如果使用VS Code和Rust扩展),请将鼠标悬停在CorsOptions上。

1

借助此处的答案和这个 GitHub 评论,我已经让我的服务与 Rocket v0.5-rc 兼容。

对于我的用例,我没有必要为特定请求定制 CORS 标头。如果您有更复杂的需求,请使用 rocket_cors

创建一个 CORS Fairing 来在每个响应上设置 CORS 标头。

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

然后使用rocket::build().attach(cors::CORS)将此挡板固定在火箭上,就可以开始了。

完全归功于以上答案,因为它们给了我很大的启发。重要的部分是为所有选项请求设置NoContent状态,这一点之前缺失了。幸运的是Domenico Gaeni的示例包括了这一点:

response.set_status(Status::NoContent)

没有这个,Rocket 每次选项请求都会 404。通过在 fairing 中设置状态,无需定义任何特殊路由来服务选项请求。

如果您正在使用此功能,请注意需要自定义 Access-Control-Allow-Origin 标头以适应您的用例。我将其硬编码为 http://localhost:3000,因为我无法让自己在某些情况下完全复制和粘贴 *

在实际生活中,您可能希望基于请求 origin headerRocket.toml config 或两者都设置 Access-Control-Allow-Origin,以支持本地开发和测试/暂存/生产服务器。


这个解决方案有一个警告,即Rocket会为每个预检请求记录404日志。在浏览器查看预检响应头并忽略状态码的情况下(至少在Safari中),它可以正常工作。但是,服务器日志很烦人。您可以在设置状态后添加此hack来抑制错误日志:let _ = response.body_mut().take(); - Dave Teare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接