How to add CORS http header?

2.3k views Asked by At

I'm trying to make a web app with React in the frontend and Rust as the backend. I thought I'd use gRPC through the Tonic crate, to communicate between the front and back end. But I'm getting the error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:50051/helloworld.Users/GetUsers. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

Any ideas how I could add the CORS header with Tonic?

2

There are 2 answers

3
Mika Vatanen On BEST ANSWER

grpc-web interoperability can be achieved through tonic-project crate tonic-web:

#[derive(Default)]
struct MyUsers;

#[tonic::async_trait]
impl UsersServer for MyUsers {
    // ...
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let my_service = MyUsers::default() // .. standard way to create tonic-service

    let service = tonic_web::config()
         .allow_origins(vec!["http://example.com"])
         .enable(UsersServerServer::new(my_service));
     
    Server::builder()
         .accept_http1(true)
         .add_service(service)
         .server("[::1]:50051".parse().unwrap()).await?;

    Ok(())
}

See https://github.com/hyperium/tonic/pull/455 and https://docs.rs/tonic-web/latest/tonic_web/

0
Shane On

The tonic_web api has changed since the accepted answer. Config has been removed, so CORS is no longer configurable using tonic_web directly, as apparently the implementation was not standards compliant.

Instead you need to add two layers - GrpcWebLayer to handle the grpc-web stuff, and CorsLayer from the tower-http crate to enable CORS.

Here's a sample application using tonic, tonic_web and CORS.

/Cargo.toml

[package]
name = "tonic_cors_example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tonic = "0.8.3"
tonic-web = "0.5.0"
prost = "0.11.3"
tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.3.5", features = ["cors"] }
http = "0.2.8"

[build-dependencies]
tonic-build = "0.8.4"

/build.rs

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("proto/helloworld.proto")?;
    Ok(())
}

/proto/helloworld.proto

syntax = "proto3";
package helloworld;

service Greeter {
    // Our SayHello rpc accepts HelloRequests and returns HelloReplies
    rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
    // Request message contains the name to be greeted
    string name = 1;
}

message HelloReply {
    // Reply contains the greeting message
    string message = 1;
}

/src/main.rs

use tonic::{transport::Server, Request, Response, Status};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
use tonic_web::GrpcWebLayer;
use tower_http::cors::{Any, CorsLayer};
use http::Method;

pub mod hello_world {
    tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(
        &self,
        request: Request<HelloRequest>, // Accept request of type HelloRequest
    ) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
        println!("Got a request from: {:?}", request);

        let reply = hello_world::HelloReply {
            message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
        };

        Ok(Response::new(reply)) // Send back our formatted greeting
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:3000".parse()?;
    let greeter = GreeterServer::new(MyGreeter::default());

    println!("GreeterServer listening on {}", addr);

    let cors = CorsLayer::new()
        // allow any headers
        .allow_headers(Any)
        // allow `POST` when accessing the resource
        .allow_methods([Method::POST])
        // allow requests from below origins
        .allow_origin(["http://localhost:5000".parse()?, "https://localhost:5001".parse()?]);

    Server::builder()
        .accept_http1(true)
        .layer(cors)
        .layer(GrpcWebLayer::new())
        .add_service(greeter)
        .serve(addr)
        .await?;

    Ok(())
}