This is a POC of a server listening to both HTTP and gRPC with autogenerated server and client implementations, and swagger documentation.
The definition is stored in pkg/application/proto/service.proto
. It has some annotations for swagger documentation, but the definition is as minimum as follows:
syntax = "proto3";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
option go_package = "github.com/tembleking/test-grpc-gateway/pkg/application/proto";
// The description of the test service
service TestService {
// The description of the method
rpc Test(google.protobuf.Empty) returns (TestResponse) {
option (google.api.http) = {
get: "/test"
};
}
}
// TestResponse is the response message for the Test method.
message TestResponse {
// Message is the test message.
string message = 1;
}
As you can see this is a minimum hello world service that will listen to /test
and will return a response with a message
field.
The three files in pkg/application/proto/.*pb.*go
are autogenerated. The other three pkg/application/proto/buf.*
files are for buf
to generate the code.
The swagger definition is automatically generated in pkg/application/http/docs/swagger.swagger.yaml
.
The HTTP router implementation in pkg/application/http/router.go
just registers the endpoints defined in the Proto definition and will redirect them to the gRPC server:
options := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := proto.RegisterTestServiceHandlerFromEndpoint(ctx, mux, grpcListenAddress, options)
The gRPC server is implemented in pkg/application/grpc/grpc.go
. Contains the actual logic that will be executed in the application.
In case of an HTTP request to /test
, this will be processed here as well.
type server struct {
}
func (s *server) Test(ctx context.Context, empty *emptypb.Empty) (*proto.TestResponse, error) {
return &proto.TestResponse{Message: "Hello world!"}, nil
}
func NewServer() *grpc.Server {
grpcServer := grpc.NewServer()
server := &server{}
proto.RegisterTestServiceServer(grpcServer, server)
reflection.Register(grpcServer)
return grpcServer
}
It will just answer with a TestResponse
with a message
field set to Hello world!
.
The server can be launched with go run ./cmd/test-grpc-gateway/
. It will listen to both HTTP and gRPC on :8080
and :9090
.
If you perform an HTTP request to /test
, you will get the response:
$ http localhost:8080/test
HTTP/1.1 200 OK
Content-Length: 26
Content-Type: application/json
Date: Fri, 12 Aug 2022 10:33:30 GMT
Grpc-Metadata-Content-Type: application/grpc
{
"message": "Hello world!"
}
There is a gRPC client implementation in cmd/grpc-client/
that uses the autogenerated code in pkg/application/proto
that
will contact the server on :9090
.
func main() {
conn, err := grpc.Dial("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { ... }
defer conn.Close()
client := proto.NewTestServiceClient(conn)
response, err := client.Test(context.Background(), &emptypb.Empty{})
if err != nil { ... }
log.Printf("%s", response.Message)
}
After executing it, with the server listening, you should see the following:
$ go run ./cmd/grpc-client/
2022/08/12 10:33:30 Hello world!