grpc icon indicating copy to clipboard operation
grpc copied to clipboard

Current State of gRPC-Web and Handling CORS/Pre-flight Requests

Open ksanderer opened this issue 11 months ago • 10 comments
trafficstars

Hi there,

First of all, thank you for maintaining this project!

I have a question regarding the current state of gRPC-Web support:

  1. Is anyone using gRPC-Web in production? If so, are there any best practices or recommendations for configuring the library to work with browsers using the grpc-web-text format?
  2. What is the proper way to handle CORS and pre-flight requests when using gRPC-Web?

I’ve spent the past few days trying to come up with a robust solution for handling CORS and pre-flight requests without forking the repository and rewriting significant parts of the codebase, but I’ve struggled to find a clean approach.

Any guidance, examples, or advice on this topic would be greatly appreciated!

Thanks in advance for your help!

ksanderer avatar Dec 16 '24 17:12 ksanderer

Hi @ksanderer I may be wrong but we do not yet provide support for grpc-web. Am I correct @polvalente? We have support for grpc http transcoding but not for grpc web.

We would have to investigate what would be required to implement such support. But you can use a envoy proxy to obtain this feature outside of this library.

sleipnir avatar Dec 16 '24 18:12 sleipnir

Oh, I see now why I failed so badly 🤣. I know about Envoy Proxy, but I thought I can avoid adding extra technology to my stack.

Actually, it looks like we are quite close to this milestone. Do you understand what needs to be done to add support for gRPC-web? Maybe I can manage this and make a PR.

What I have already found is missing:

  1. CORS support
  2. Trailers for web (?) Not sure. When I patched the HTTP2 protocol to add CORS as in this issue, I started receiving errors that Trailers are missing, but I haven't delved deeply into this issue.
  3. Anything else?

ksanderer avatar Dec 16 '24 19:12 ksanderer

If nothing has changed after Google passed the grpc baton I think we would have to be guided by this document here https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md

sleipnir avatar Dec 16 '24 19:12 sleipnir

I think before going to Envoy, it looks like there's only a small amount of things we're missing. @ksanderer would you be up for at least enumerating what we need?

polvalente avatar Dec 17 '24 05:12 polvalente

Yes of course @polvalente, my suggestion was for the scenario where he couldn't wait for the functionality. grpc-web is quite different and simpler than the standard protocol. It would basically encode messages in base64 and a few other small things.

sleipnir avatar Dec 17 '24 12:12 sleipnir

I researched the topic a little (the list of features below might be incomplete and incorrect, as it is based on my high-level understanding of the codebase and the grpc-web protocol itself).

It seems I initially underestimated the amount of work involved. I marked with a checkbox the parts I have seen in the codebase that appear to be working, but I haven't performed conformance tests.

So, it looks like Envoy would be a right solution right now 😅

  • [x] 1. gRPC-Web works over both HTTP/1.1 and HTTP/2

  • [x] 2. Content-Type Handling

    • For unary and streaming requests: application/grpc-web
    • For requests with base64-encoded payloads (to handle binary data in browsers): application/grpc-web-text
  • [x] 3. Base64 Encoding for gRPC-Web-Text

    • gRPC-Web-Text uses base64 encoding for the request and response payloads to make them compatible with browser environments that cannot handle raw binary data.
    • If the Content-Type is application/grpc-web-text, we have to:
      • Decode the base64-encoded request body before processing it.
      • Encode the response body in base64 before sending it back to the client.
  • [ ] ??? 4. Framing of Messages

    • gRPC-Web uses a framing format similar to native gRPC, but the frames can be sent over HTTP/1.1:
      • Each message is prefixed with a 5-byte header:
        • 1 byte: Flags (e.g., compression flag)
        • 4 bytes: Length of the message
      • server must parse incoming frames and construct outgoing frames in this format.
  • [ ] 5. Trailers in the Response

    • In gRPC-Web, trailers (e.g., grpc-status, grpc-message) are sent differently depending on the transport:
      • HTTP/2: Trailers are sent as native HTTP/2 trailers (HEADERS frame after the DATA frames).
      • HTTP/1.1: Trailers are encoded as a special frame in the response body because HTTP/1.1 does not natively support trailers in a way accessible to browsers.
        • The trailer frame is identified by setting the most significant bit of the first byte in the 5-byte header to 1 (frame type 0x80).
        • The trailer frame contains serialized HTTP/2-style headers (e.g., grpc-status, grpc-message).
  • [ ] 6. CORS (Cross-Origin Resource Sharing)

    • Browsers enforce CORS policies, so the server must handle CORS preflight requests (OPTIONS requests) and include appropriate CORS headers in responses.
    • We have to add the following headers to the responses:
      • Access-Control-Allow-Origin: * (better to make this configurable)
      • Access-Control-Allow-Methods: POST, OPTIONS
      • Access-Control-Allow-Headers: Content-Type, X-Grpc-Web, X-User-Agent
      • Access-Control-Expose-Headers: grpc-status, grpc-message
  • [ ] 7. Unary and Streaming RPCs

    • gRPC-Web supports both unary and server-streaming RPCs:
      • [x] Unary RPCs: A single request followed by a single response.
      • [ ] ?? Server-streaming RPCs: A single request followed by multiple responses.
      • [ ] gRPC-Web does not support client-streaming or bidirectional streaming RPCs - we have to reject this requests with UNIMPLEMENTED status.
  • [ ] 8. Error Handling

    • [x] gRPC-Web uses the same status codes as gRPC (grpc-status) to indicate the outcome of an RPC:
      • 0: OK
      • 1: CANCELLED
      • 2: UNKNOWN
      • 3: INVALID_ARGUMENT
      • ...
    • [x] Include the grpc-status and grpc-message fields in the trailers (or the trailer frame for HTTP/1.1).
    • [ ] Map HTTP status codes to gRPC status codes appropriately:
      • 200 OK: RPC succeeded.
      • 400 Bad Request: Invalid request.
      • 500 Internal Server Error: Server-side error.
  • [ ] 9. Compression

    • gRPC-Web supports optional compression for request and response payloads.
      • If compression is enabled, the grpc-encoding header specifies the compression algorithm (e.g., gzip).
      • Server must decompress incoming requests and compress outgoing responses if requested by the client.
    • The gRPC-Web specification for browsers discourages the use of message-level compression in favor of browser-level stream compression.
  • [ ] 10. Metadata Handling

    • gRPC-Web supports sending and receiving metadata (key-value pairs) in the headers and trailers.
    • Incoming metadata is sent as HTTP headers (e.g., x-grpc-web-<key>).
    • Outgoing metadata can be sent in the initial response headers or in the trailers.
  • [ ] 11. gRPC-Web-Specific Headers**

    • Handle gRPC-Web-specific headers in requests and responses:
      • X-Grpc-Web: Indicates that the request is a gRPC-Web request.
      • X-User-Agent: Optional header sent by the client (e.g., grpc-web-javascript/0.1).

ksanderer avatar Dec 17 '24 16:12 ksanderer

Yes, Framing, CORS, and Trailers should be the focus of an implementation, compression, base64 codecs, and metadata are already supported or partially supported

sleipnir avatar Dec 17 '24 16:12 sleipnir

As far as I know this library does support grpc-web (and yep we use it), yes there is indeed a cors issue https://github.com/elixir-grpc/grpc/issues/237. from the top of my head, yep there might be som trailing issues/backpressure issues affecting download of huge files. However, i do think that the we are moving to http transcoding and hopefully there are no reasons to stick with grpc-web. My 2 cents

AleksandarFilipov avatar Feb 01 '25 09:02 AleksandarFilipov

I like to mention @drowzy effort here https://github.com/elixir-grpc/grpc/pull/206#issuecomment-1186731225

AleksandarFilipov avatar Feb 03 '25 12:02 AleksandarFilipov

@ksanderer maybe this https://github.com/elixir-grpc/grpc/pull/412 solves this issue?

sleipnir avatar May 29 '25 16:05 sleipnir

Hi guys @ksanderer @AleksandarFilipov. Can you verify if the recent addition of CORS support has unlocked grpc-web or if something still needs to be done to support this?

sleipnir avatar Jul 15 '25 20:07 sleipnir

FWIW, I'm using this in a (small) production environment. The CORS interceptor works, though I just filed a PR against it to improve its reliability (found in production). It's all quite reliable, otherwise -> the same protos are used to generate both the Elixir gRPC and the front-end Typescript gRPC-web code and they interoperate perfectly.

This environment currently has 3 gRPC endpoints that are used from the web UIs.

We have no proxies or other infra (other than the usual transparent ingress) between the browser and the Elixir gRPC endpoints, while the gRPC endpoints themselves are interacting with a variety of data sources on the back end. It all Just Works(tm).

aseigo avatar Aug 24 '25 09:08 aseigo

Great @aseigo thank you. Then I'll be closing this issue once we have field evidence proving the feature. If necessary, we can reopen this or create a new issue in the future.

sleipnir avatar Aug 26 '25 21:08 sleipnir