flexway
FRENTalk to an expert

A gRPC upstream

GuideOperationsSince version 1.1.0

Markdown

Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.

Goal: serve a gRPC service to gRPC clients, to a browser (gRPC-Web) and in REST/JSON, from its .proto alone.

Command - the upstream's team annotates its .proto: the standard REST mapping (google.api.http) and what Flexway needs beyond it (flexway.v1.operation, the counterpart of x-flexway-*). Then it builds the descriptor set:

syntax = "proto3";
package shop.v1;                          // the gRPC name is shop.v1.Orders/GetOrder

import "flexway/v1/options.proto";       // copy it from the tag you run: services/gateway/api/flexway/v1/options.proto
import "google/api/annotations.proto";   // from buf.build/googleapis/googleapis, below

service Orders {
  rpc GetOrder(GetOrderRequest) returns (Order) {
    option (google.api.http) = { get: "/v1/shop/orders/{id}" };
    option (flexway.v1.operation) = { upstream: "orders" security: [ { scopes: ["orders:read"] } ] };
  }
}
v=<version>   # the Flexway you run
mkdir -p flexway/v1 && curl -fsSLo flexway/v1/options.proto \
  "https://raw.githubusercontent.com/flexwiz/flexway/v$v/services/gateway/api/flexway/v1/options.proto"
cat > buf.yaml <<'YAML'
version: v2
deps: [buf.build/googleapis/googleapis]
YAML
buf dep update
buf build -o orders.binpb       # or: protoc --include_imports --descriptor_set_out=orders.binpb ...
contracts:
  protobuf: [./orders.binpb]
upstreams:
  orders: { url: http://orders.internal:9000, protocol: grpc }   # h2c inside a mesh

Check:

grpcurl -plaintext -protoset orders.binpb -H "authorization: Bearer $TOKEN" \
  -d '{"id":"42"}' localhost:8080 shop.v1.Orders/GetOrder               # gRPC, on the traffic port
curl -H "authorization: Bearer $TOKEN" localhost:8080/v1/shop/orders/42  # the same operation in REST

Both calls are one operation, shop.v1.Orders/GetOrder: same identity, same rate, same quota, same audit line. A refusal reaches a gRPC client as a google.rpc.Status (the problem code in ErrorInfo.reason), a REST client as RFC 9457. An error of the upstream reaches a REST client as an upstream_status problem, its gRPC status in the grpc extension. A server stream is a text/event-stream in REST.

For a browser (gRPC-Web), the cors policy exposes grpc-status, grpc-message and grpc-status-details-bin (expose_headers) and accepts x-grpc-web, x-user-agent and grpc-timeout (allow_headers): without them, the browser cannot read a refusal. Refused at load at this version: a variable spanning segments ({name=shelves/*}), a wildcard, a verb (:cancel), google.api.http on a client or bidirectional stream - those stay served in gRPC only.

Next steps