Skip to content

Support gRPC-Web so browsers can call the API without a proxy #296

Description

@vincenzopalazzo

Summary

The gRPC API can't be called from a browser today, so any web frontend has to run a gRPC-Web proxy (Envoy, grpcwebproxy, ...) in front of the node. Accepting gRPC-Web directly would remove that extra hop and keep ldk-server a single binary for that use case.

Why a browser can't call the API today

Three code-level blockers, plus one deployment concern:

  1. The status lives in HTTP/2 trailers. GrpcBody delivers grpc-status / grpc-message as http_body::Frame::trailers (ldk-server-grpc/src/grpc.rs). The browser fetch API cannot read trailers, so a page has no way to tell success from failure on any response that carries a body. (The Empty / trailers-only path puts the status in the response headers, which a browser can read — so errors raised before the handler runs are visible, but normal responses are not.) gRPC-Web exists to solve exactly this: the trailers go out as a length-prefixed body frame whose flag byte has the MSB set (0x80).

  2. The content type is rejected. validate_grpc_request accepts only application/grpc and application/grpc+proto; a browser client sends application/grpc-web+proto.

  3. No CORS. There is no OPTIONS handling and no Access-Control-* headers anywhere, so a page on any other origin is blocked before the request leaves. The trailers-only case above would additionally need Access-Control-Expose-Headers: grpc-status, grpc-message.

  4. Self-signed TLS (deployment concern, not a code change). The auto-generated cert at <storage_dir>/tls.crt is meant to be pinned by clients, and page JavaScript cannot pin a certificate — an operator exposing the API to a browser would front it with a normally-trusted cert.

HTTP/2 itself is not a blocker: ALPN already advertises h2 (ldk-server/src/util/tls.rs) and browsers speak HTTP/2.

What would not have to change

The HMAC scheme is unaffected. x-auth signs timestamp_be_bytes || grpc_request_body_bytes including the 5-byte frame header, and a gRPC-Web request body is byte-identical to a gRPC one — only the response framing and the headers differ. A browser can compute the same signature with WebCrypto, and the signature also survives a translating proxy unchanged.

There is already precedent in the service for a non-gRPC-shaped request: GET /metrics returns GrpcBody::Plain from the same NodeService.

Possible shape of the change

Roughly:

  • accept application/grpc-web+proto and application/grpc-web in validate_grpc_request, and carry which dialect the request used through to the response
  • for gRPC-Web requests, end the body with a 0x80-flagged length-prefixed trailers frame instead of HTTP trailers — GrpcBody::Unary and GrpcBody::Stream already own that ending
  • answer OPTIONS preflight, and set Access-Control-Allow-Origin, Access-Control-Allow-Headers: content-type, x-grpc-web, x-auth, and Access-Control-Expose-Headers: grpc-status, grpc-message
  • gate the CORS part behind config (e.g. [api] cors_allowed_origins = [], empty by default) so existing deployments are untouched and the browser surface is opt-in

SubscribeEvents is fine under gRPC-Web — server streaming is supported by the protocol; only client-streaming and bidirectional streaming are not, and the API has neither.

Context

I'm building a browser-based management UI against this API and currently ship an Envoy config alongside it to do the translation. It works, and the HMAC auth passes through it untouched, but it's an extra process to deploy and configure for what is otherwise a single-binary node.

Happy to put a patch together if this direction sounds reasonable — wanted to check the appetite and preferred shape first.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions