Skip to content

Commit

Permalink
Create separate protobuf message for error status.
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinKuzma committed Aug 20, 2024
1 parent 3f26130 commit 9f0790d
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 49 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,7 @@ docker:
go mod vendor
docker build . -t grpc-rest-proxy-test

generate:
buf generate

.PHONY: all default build test fmt lint build-only test-only race cover coverprofile clean
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ spec:
```
### Error handling
On error proxy returns HTTP status code and JSON response body. JSON is defined using Google's RPC status message. It contains code, message and details.
On error, the proxy returns an HTTP status code and JSON response body. JSON is defined using our [Error protobuf message](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto). It contains code, message and details.
Backend endpoint can define its own protobufs containing details of error and return it in standard grpc status. Proxy then takes this status and serializes it to JSON response.
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.
```json
{
Expand Down
11 changes: 11 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: v2
managed:
enabled: true
disable:
- module: buf.build/googleapis/googleapis
plugins:
- remote: buf.build/protocolbuffers/go:v1.34.2
out: pkg
opt: paths=source_relative
inputs:
- directory: protos
11 changes: 11 additions & 0 deletions buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: v2
modules:
- path: protos
deps:
- buf.build/googleapis/googleapis
lint:
use:
- DEFAULT
breaking:
use:
- FILE
47 changes: 10 additions & 37 deletions pkg/transport/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import (
"github.com/eset/grpc-rest-proxy/pkg/service/jsonencoder"
routerPkg "github.com/eset/grpc-rest-proxy/pkg/service/router"
"github.com/eset/grpc-rest-proxy/pkg/service/transformer"
statusPkg "github.com/eset/grpc-rest-proxy/pkg/transport/status"

jErrors "github.com/juju/errors"
rpcStatus "google.golang.org/genproto/googleapis/rpc/status"

"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
grpcStatus "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/dynamicpb"
"google.golang.org/protobuf/types/known/anypb"
)

type ProxyEndpoint struct {
Expand All @@ -44,23 +44,23 @@ func NewProxyEndpoint(
}
}

func (e *ProxyEndpoint) Handle(w http.ResponseWriter, r *http.Request) {
func (e *ProxyEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method, err := routerPkg.StringToMethod(r.Method)
if err != nil {
e.respondWithError(r.Context(), w, errStatusFromCode(http.StatusMethodNotAllowed))
e.respondWithError(r.Context(), w, statusPkg.FromHTTPCode(http.StatusMethodNotAllowed))
return
}

routeMatch := e.router.Find(method, r.URL.Path)
if routeMatch == nil {
e.respondWithError(r.Context(), w, errStatusFromCode(http.StatusNotFound))
e.respondWithError(r.Context(), w, statusPkg.FromHTTPCode(http.StatusNotFound))
return
}

rpcRequest, err := convertRequestToGRPC(routeMatch, r)
if err != nil {
e.logger.ErrorContext(r.Context(), jErrors.Details(jErrors.Trace(err)))
e.respondWithError(r.Context(), w, errStatusFromCode(http.StatusBadRequest))
e.respondWithError(r.Context(), w, statusPkg.FromHTTPCode(http.StatusBadRequest))
return
}
rpcResponse := transformer.GetRPCResponse(routeMatch.GrpcSpec.ResponseDesc)
Expand All @@ -75,13 +75,13 @@ func (e *ProxyEndpoint) Handle(w http.ResponseWriter, r *http.Request) {
grpc.Trailer(&trailer),
)
if err != nil {
if status, ok := grpcStatus.FromError(err); ok {
if errStatus, ok := grpcStatus.FromError(err); ok {
transformer.SetRESTHeaders(r.ProtoMajor, w.Header(), header, trailer)
e.respondWithError(r.Context(), w, errStatusFromGRPC(status))
e.respondWithError(r.Context(), w, statusPkg.FromGRPC(errStatus))
return
}
e.logger.ErrorContext(r.Context(), jErrors.Details(jErrors.Trace(err)))
e.respondWithError(r.Context(), w, errStatusFromCode(http.StatusInternalServerError))
e.respondWithError(r.Context(), w, statusPkg.FromHTTPCode(http.StatusInternalServerError))
return
}

Expand All @@ -102,7 +102,7 @@ func (e *ProxyEndpoint) Handle(w http.ResponseWriter, r *http.Request) {
}
}

func (e *ProxyEndpoint) respondWithError(ctx context.Context, w http.ResponseWriter, status *rpcStatus.Status) {
func (e *ProxyEndpoint) respondWithError(ctx context.Context, w http.ResponseWriter, status *statusPkg.Error) {
encodedStatus, err := e.jsonEncoder.Encode(status)
if err != nil {
e.logger.ErrorContext(ctx, jErrors.Details(jErrors.Trace(err)))
Expand All @@ -118,33 +118,6 @@ func (e *ProxyEndpoint) respondWithError(ctx context.Context, w http.ResponseWri
}
}

func errStatusFromCode(code int) *rpcStatus.Status {
return &rpcStatus.Status{
Code: int32(code),
Message: http.StatusText(code),
}
}

func errStatusFromGRPC(status *grpcStatus.Status) *rpcStatus.Status {
httpStatus := transformer.GetHTTPStatusCode(status.Code())

msg := status.Message()
if msg == "" {
msg = http.StatusText(httpStatus)
}

var details []*anypb.Any
if status.Proto() != nil {
details = status.Proto().GetDetails()
}

return &rpcStatus.Status{
Code: int32(httpStatus),
Message: msg,
Details: details,
}
}

func getQueryVariables(queryValues url.Values) []transformer.Variable {
var queryVariables []transformer.Variable
for name, values := range queryValues {
Expand Down
18 changes: 9 additions & 9 deletions pkg/transport/endpoint_reloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,30 @@ import (

// EndpointReloader is wrapper for Handle method to allow dynamic endpoint reloading
type EndpointReloader struct {
mtx sync.RWMutex
proxy *ProxyEndpoint
mtx sync.RWMutex
handler http.Handler
}

func NewEndpointReloader(proxy *ProxyEndpoint) *EndpointReloader {
func NewEndpointReloader(handler http.Handler) *EndpointReloader {
return &EndpointReloader{
proxy: proxy,
handler: handler,
}
}

func (e *EndpointReloader) Handle(w http.ResponseWriter, r *http.Request) {
func (e *EndpointReloader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
e.mtx.RLock()
defer e.mtx.RUnlock()
e.proxy.Handle(w, r)
e.handler.ServeHTTP(w, r)
}

func (e *EndpointReloader) Set(endpoint *ProxyEndpoint) {
e.mtx.Lock()
e.proxy = endpoint
e.handler = endpoint
e.mtx.Unlock()
}

func (e *EndpointReloader) Endpoint() *ProxyEndpoint {
func (e *EndpointReloader) Endpoint() http.Handler {
e.mtx.RLock()
defer e.mtx.RUnlock()
return e.proxy
return e.handler
}
181 changes: 181 additions & 0 deletions pkg/transport/status/error.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9f0790d

Please sign in to comment.