LogoPly
Features

gRPC Gateway

Expose unary gRPC methods over JSON HTTP routes.

gRPC Gateway

The grpcgateway package maps gRPC-style method paths to JSON HTTP handlers. It handles JSON to protobuf conversion and delegates the call to an invoker.

import "github.com/Dziqha/ply/grpcgateway"

gateway, err := grpcgateway.New(grpcgateway.Options{
	Resolver: func(method string) (proto.Message, proto.Message, error) {
		switch method {
		case "/users.v1.UserService/GetUser":
			return &usersv1.GetUserRequest{}, &usersv1.GetUserResponse{}, nil
		default:
			return nil, nil, grpcgateway.ErrUnknownMethod
		}
	},
	Invoker: func(ctx context.Context, method string, request, response proto.Message) error {
		return conn.Invoke(ctx, method, request, response)
	},
})
if err != nil {
	log.Fatal(err)
}

gateway.Mount(app, "/grpc")

conn is a grpc.ClientConnInterface, such as *grpc.ClientConn. The gateway package does not import the gRPC runtime; the invoker is supplied by the application.

HTTP mapping

A request to:

POST /grpc/users.v1.UserService/GetUser
Content-Type: application/json

{"id": "42"}

is converted to the full method /users.v1.UserService/GetUser. The request JSON is unmarshaled into the request message, passed to the invoker, and the response message is marshaled back to JSON.

HTTP/1.1 200 OK
Content-Type: application/json

{"user":{"id":"42","name":"Ada"}}

Mount registers POST <prefix>/* and hides the catch-all route from the OpenAPI document. The returned route can be unhidden if the gateway needs to appear in generated documentation.

Handler without Mount

Handler can be mounted on a service-specific route:

app.Post("/users.v1.UserService/*", gateway.Handler())

The path must contain exactly <service>/<method> after the optional prefix. Requests with any other shape return 404.

Options

OptionDefaultPurpose
ResolverrequiredReturns fresh request and response messages
InvokerrequiredCalls the gRPC method
MaxBodyBytes4 MiBMaximum JSON request body
Marshalprotojson.MarshalOptions{}Response JSON options
Unmarshalprotojson.UnmarshalOptions{}Request JSON options
StatusMappernoneMaps invoker errors to HTTP status and message

The resolver must return a new request and response message for each call. Protobuf messages must not be shared across concurrent requests.

Request context

RequestContext returns the Ply context from the invoker context. Use it to read request headers or values set by middleware:

Invoker: func(ctx context.Context, method string, request, response proto.Message) error {
	if c := grpcgateway.RequestContext(ctx); c != nil {
		token := c.Header("Authorization")
		// Attach the token to the outgoing gRPC metadata.
	}
	return conn.Invoke(ctx, method, request, response)
},

ctx also carries cancellation and deadlines from the HTTP request.

Errors

By default, invoker errors return 500 without exposing the error text. Return *grpcgateway.Error for an explicit HTTP status:

return &grpcgateway.Error{
	Status:  http.StatusNotFound,
	Message: "user not found",
	Err:     err,
}

Use StatusMapper to translate gRPC status codes:

StatusMapper: func(err error) (int, string) {
	st, ok := status.FromError(err)
	if !ok {
		return 500, "internal server error"
	}
	switch st.Code() {
	case codes.NotFound:
		return 404, st.Message()
	case codes.InvalidArgument:
		return 400, st.Message()
	case codes.PermissionDenied:
		return 403, st.Message()
	default:
		return 500, "internal server error"
	}
},

status and codes are from google.golang.org/grpc, which the application already uses when it has a gRPC client.

Notes

  • Only unary methods are supported. Client streaming, server streaming, and bidirectional streaming need a different transport.
  • Requests use application/json or a +json media type. Other content types return 415.
  • An empty request body is treated as {}.
  • protojson rejects unknown fields by default. Set Unmarshal.DiscardUnknown only when the API must accept forward-compatible payloads.
  • Authentication middleware should run before the gateway route and attach the identity to the request context.