hyperium / tonic

A native gRPC client & server implementation with async/await support.
https://docs.rs/tonic
MIT License
9.76k stars 997 forks source link

Support grpc-web by adding Cross-Origin Resource Sharing #270

Closed shadowmint closed 1 year ago

shadowmint commented 4 years ago

Feature Request

Crates

tonic = "0.1.1"

Motivation

When calling tonic service from grpc-web clients, the result is invariably:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:10001/routeguide.RouteGuide/ListFeatures. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

This is because the HTTP/2 request being made is preceeded by a pre-flight CORS request to determine if the request should be allowed, for more detail, background, etc. see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

By adding this, tonic based grpc services could be called directly from grpc-web clients, without the need for a proxy such as envoy.

Proposal

When creating the server, add a section to allow CORS options to be specified, eg.

    let svc = RouteGuideServer::new(route_guide);
    Server::builder()
        .max_concurrent_streams(256)
        .tls_config(..)
        .add_service(svc)
        .serve(addr)
        .await?;

Add a new cors_config() method that allows the CORS headers to be specified, ie:

    let remote_client = "127.0.0.1:3000".parse().unwrap();
    let svc = RouteGuideServer::new(route_guide);
    Server::builder()
        .cors_config(CorsConfig { remotes: vec!(remote_client)})
        .add_service(svc)
        .serve(addr)
        .await?;

Alternatives

https://github.com/hyperium/tonic/tree/master/examples/src/interceptor shows how an interceptor can be used to process requests; however, because the OPTIONS request for CORS is a pre-flight check, the interceptor is never invoked for the request.

https://github.com/hyperium/tonic/tree/master/examples/src/hyper_warp shows how a warp server can be used along side the server implementation. However, it is unclear if this is suitable as it appears that the demonstrated implementation is A/B, where the B requests are handled entirely by the GRPC service and the A requests entirely by the warp service. ie. You cannot handle the OPTIONS request from B requests in A. However, perhaps I simply don't understand this example and it is suitable.

You could also simply require that a proxy is used, eg. envoy. However, the downside of this is that although it is probably the correct 'production' solution, just as we wish to be able to run the service locally without having a copy of apache/nginx/etc, the developer experience is very poor. Having a local webserver for frontend development on say, localhost:3000, and wanting to have a local dev server to directly connect to is an extremely common development workflow. The setup required for this is covered in reasonable detail here: https://blog.envoyproxy.io/envoy-and-grpc-web-a-fresh-new-alternative-to-rest-6504ce7eb880, although it omits the deeply irritating process of getting envoy to run on windows at all.

Ultimately, this is a developer papercut.

You setup a tonic grpc service, and out of the box, grpc-web doesn't work, and there's no work around for it other than running a local proxy server.

alce commented 4 years ago

Hi @shadowmint

Enabling CORS is not enough for Tonic's transport to handle grpc-web requests, since the protocol is different. We either need a proxy to translate requests/responses to/from grpc and grpc-web or a custom transport to handle grpc-web requests.

The latter solution was proposed by Lucio elsewhere and I think it's the best option. I did some work around this a while back but haven't been able to finish it.

LucioFranco commented 4 years ago

Yeah, grpc-web is a different protocol than what tonic currently implements, so this behavior is expected. Right now, tonic only supports gRPC over http2 and if you want grpc-web support you will most likely need to use some sort of proxy. As @alce we have looked into support a different transport that can handle this but no one has really fully championed the work :)

zancas commented 4 years ago

Hi @shadowmint

Enabling CORS is not enough for Tonic's transport to handle grpc-web requests, since the protocol is different. We either need a proxy to translate requests/responses to/from grpc and grpc-web or a custom transport to handle grpc-web requests.

The latter solution was proposed by Lucio elsewhere and I think it's the best option. I did some work around this a while back but haven't been able to finish it.

@alce @LucioFranco this sounds like an interesting project. Is your initial work available somewhere?

alce commented 4 years ago

@zancas Not mine. The implementation I am using only handles grpc-web+proto unary calls. It works for me for now but publishing it would probably do more harm than good for anybody else. I'll clean it up soon-ish, I hope.

If this interests you, I could publish a gist somewhere although the code it's not ready for production and it's a bit of an eye sore.

There is also this PR https://github.com/hyperium/tonic/pull/288 . I have not looked at it though but it's probably better than mine.

onelson commented 4 years ago

Feeling like I don't have much to add to the conversation but when I did a proof of concept grpc-web setup with tonic, I used envoy to proxy the tonic service. You can configure the CORS headers in envoy. Having envoy in the middle took care of the conversion from http 1 -> 2, keeping clients and servers both happy without having to concern themselves with extra baggage.

I wrote a little about this here: https://github.com/onelson/e2e-rs/tree/grpc#envoy

onsails commented 4 years ago

I am using gloo (which is using envoy under the hood) to expose tonic-based services to web-based clients (which use grpc-web protoc plugin) – it works like a charm.

softdevca commented 2 years ago

grpc-web should now be handled directly by tonic-web

davidpdrsn commented 2 years ago

And cors can be done with https://docs.rs/tower-http/0.3.4/tower_http/cors/index.html

LucioFranco commented 1 year ago

https://github.com/hyperium/tonic/pull/1325 will fix enable to work correctly and make adding cors easier like it was before.