Copyright © ESET 2024-2028
Service that is automatically translating REST requests to gRPC and sending them to provided endpoint. REST is defined using Google's proto HTTP annotation. Our implementation is able to load proto descriptors directly from grpc endpoint using server reflection service. Main advantage of this approach is elimination of the need to recompile protobufs or grpc-rest-proxy (to incorporate changes in protos), making translation and maintenance easier.
make build
For more information see: config-example.yaml or cmd/service/main.go.
Usage of ./grpc-rest-proxy:
-c, --config string path to config file
--descriptors.remote.exclude stringArray remote descriptors to exclude (default [grpc.health.v1.Health,grpc.reflection.v1.ServerReflection])
--descriptors.remote.reflectionServiceName string reflection service name (default "grpc.reflection.v1.ServerReflection/ServerReflectionInfo")
--descriptors.remote.timeout duration request timeout for remote descriptors (default 1m0s)
--gateways.grpc.client.requestTimeout duration requests timeout (default 5s)
--gateways.grpc.client.targetAddr string address and port of the gRPC server (default "0.0.0.0:50051")
--gateways.grpc.client.tls use TLS for gRPC connection
--gateways.grpc.client.tlsSkipverify skip TLS verification
--gateways.grpc.requestTimeout duration client request timeout (default 5s)
--transport.http.maxRequestSizeKB uint maximum size of requests in KB (default 10024)
--transport.http.requestTimeout duration request timeout (default 5s)
--transport.http.server.addr string address and port of the HTTP server (default "0.0.0.0:8080")
--transport.http.server.gracefulTimeout duration graceful timeout (default 5s)
--transport.http.server.readHeaderTimeout duration read header timeout (default 5s)
--transport.http.server.readTimeout duration read timeout (default 10s)
--service.jsonencoder.useProtoNames use proto names in JSON response (instead of camel case)
--service.jsonencoder.emitUnpopulated emit unpopulated fields in JSON response for empty gRPC values
--service.jsonencoder.emitDefaultValues include default values in JSON response for empty gRPC values
-v, --version print version
- command line args
- config file
- default flags
All default values can be found in cmd/service/main.go.
Two methods are currently supported: remote proto reflection server or local storage. Remote proto reflection server is used as default method when no other is specified by configuration or parameters.
During startup, proto descriptors will be downloaded from endpoint specified in gateways
section. Default endpoint address is 0.0.0.0:50051
.
descriptors:
kind: remote
remote:
# overall timeout for connecting and loading all definitions
timeout: 1m
reflectionServiceName: grpc.reflection.v1.ServerReflection/ServerReflectionInfo
# which services we want to exclude from parsing (making them private)
exclude:
- grpc.health.v1.Health
- grpc.reflection.v1.ServerReflection
- grpc.reflection.v1alpha.ServerReflection
Proto reflection is allowed by adding following lines:
import (
"fmt"
"net"
logging "log/slog"
jErrors "github.com/juju/errors"
"github.com/spf13/pflag"
"google.golang.org/grpc"
+ "google.golang.org/grpc/reflection"
pb "github.com/eset/grpc-rest-proxy/cmd/examples/grpcserver/gen/user/v1"
)
func newServer() *grpc.Server {
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, NewUserService())
// Register reflection service on gRPC server.
+ reflection.Register(grpcServer)
return grpcServer
}
Local directory will be recursively searched during startup for compiled proto descriptors (file extension .desc
).
descriptors:
kind: "local"
local:
dir: "/var/opt/myprotos/"
You can run grpc-rest-proxy as a sidecar along with grpc service. All you need to do is supply image with configuration and update deployment of your service.
Advantages of sidecar:
- Loaded proto definitions in gateway will always match service's.
- Compatible with cannary releases.
- Gateway's lifetime is tied to service, thus easier to manage.
Gateway must be set to load from remote proto repository. In this mode, it will automatically load all definitions from targeted endpoints in its configuration. Example: config-sidecar.yaml.
Example of Kubernetes deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-service
spec:
replicas: 4
template:
metadata:
spec:
containers:
# main service with enabled grpc reflection
- name: service
image: "example-service:latest"
command:
- "/app/example-service"
ports:
- name: grpc-port
containerPort: 50051
# gateway that will connnect to service
- name: grpc-rest-proxy
image: grpcrestproxy.azurecr.io/grpc-rest-proxy:latest
command:
- "/app/grpc-rest-proxy"
args:
- "--gateways.grpc.client.targetAddr=127.0.0.1:50051"
ports:
- name: web-port
containerPort: 8080
On error, the proxy returns an HTTP status code and JSON response body. JSON is defined using our Error protobuf message. It contains code, message and details.
The backend endpoint can define its own protobuf messages containing details of the error and return it in standard grpc status. The proxy takes these messsages and serializes them into JSON response.
{
"code": 404,
"message": "User name not found.",
"details": [
{
"@type": "type.googleapis.com/user.v1.GetUserError",
"username": "John1234",
"recommendation": "Please check the username and try again."
}
]
}