goprotocol-buffersgrpcgrpc-gogrpc-gateway

Serialization problem in grpc gateway(go)


I'm trying to create a microservice app using grpc gateway, but faced with a small problem. When I try to send request to api, my fields initialize as zero-value. It's the first time when I'm trying grpc gateway so the problem can be kinda stupid.

Here's my request:

{
    "amount": 20000,
    "bill": 1010,
    "email": "aaa@www.com",
    "employeeID": 1,
    "userID": 1,
    "orgID": 1,
    "feedback": "good",
    "grade": 5,
    "isCommissionCompensated": true,
    "redirectURL": "https://google.com",
    "failRedirectURL": "https://google.com",
    "paymentMethod": "CARD"
}

Here's the proto file of grpc gateway

syntax = "proto3";

option go_package = "/paymentProto";

import "google/api/annotations.proto";

package payment;

service PaymentService {
    rpc PayIn(PayInRequest) returns(PayInResponse) {
        option(google.api.http) = {
            post: "/payment/pay_in"
        };
    }
}

message PayInRequest {
    uint64 userID = 1;
    uint64 employeeID = 2;
    uint64 orgID = 3;
    string redirectURL = 4;
    string failRedirectURL = 5;
    double amount = 6;
    string email = 7;
    string feedback = 8;
    uint32 grade = 9;
    bool isCommissionCompensated = 10;
    optional double bill = 11;
    string paymentMethod = 12;
}

message PayInResponse {
    string payInForm = 1;
}

I have a method that initialize my server

func (s *Server) Run() error {
    s.MapHandlers()

    var eg errgroup.Group

    eg.Go(s.RunPaymentGRPC)
    eg.Go(s.RunPaymentHTTP)

    if err := eg.Wait(); err != nil {
        return errors.Wrap(err, "Server.Run")
    }

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    logger.Info("Shutting down Payment server...")
    s.grpcServer.GracefulStop()
    logger.Info("Success!")

    return nil
}

And 3 methods that initialize the gateway:

func (s *Server) MapHandlers() {

    logger.Info("Trying to map handlers...")

    paymentUC := paymentUsecase.NewPaymentUC()
    paymentHandlers := paymentDelivery.NewHandlers(paymentUC)

    paymentProto.RegisterPaymentServiceServer(s.grpcServer, paymentHandlers)

    logger.Info("Success!")
}

func (s *Server) RunPaymentHTTP() error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    logger.Info("Trying to run http server")

    opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}

    err := paymentProto.RegisterPaymentServiceHandlerFromEndpoint(ctx, s.mux, s.cfg.PaymentServer.Grpc.Host, opts)
    if err != nil {
        return errors.Wrap(err, "failed to register gRPC gateway")
    }

    logger.Infof("Serving http on %s", s.cfg.PaymentServer.Http.Host)
    if err := http.ListenAndServe(s.cfg.PaymentServer.Http.Host, s.mux); err != nil {
        return errors.Wrap(err, "failed to serve")
    }

    logger.Info("Success!")
    return nil
}

func (s *Server) RunPaymentGRPC() error {
    lis, err := net.Listen("tcp", s.cfg.PaymentServer.Grpc.Host)
    if err != nil {
        return errors.Wrapf(err, "failed to listen: %v", s.cfg.PaymentServer.Grpc.Host)
    }

    logger.Infof("Serving gRPC on %s", s.cfg.PaymentServer.Grpc.Host)
    if err := s.grpcServer.Serve(lis); err != nil {
        return errors.Wrap(err, "failed to serve gRPC server")
    }

    return nil
}

And here's the struct that implements my genered proto file code

type PaymentHandlers struct {
    paymentUC PaymentUC
    paymentProto.UnimplementedPaymentServiceServer
}

func NewHandlers(paymentUC PaymentUC) *PaymentHandlers {
    return &PaymentHandlers{
        paymentUC: paymentUC,
    }
}

func (h *PaymentHandlers) PayIn(
    ctx context.Context,
    request *paymentProto.PayInRequest,
) (*paymentProto.PayInResponse, error) {
    ctx, span := otel.Tracer("api-gateway").Start(ctx, "PaymentHandlers.PayIn")
    defer span.End()

    return &paymentProto.PayInResponse{
        PayInForm: "success",
    }, nil
}

And when I try to send request, my genered struct has such fieldsenter image description here

What can be the problem? Thank u in advance!


Solution

  • Your service proto is missing the body: "*" which may be causing the issue. Can you try adding it to see if it fixes the issue?

    service PaymentService {
        rpc PayIn(PayInRequest) returns(PayInResponse) {
            option(google.api.http) = {
                post: "/payment/pay_in"
                body: "*"
            };
        }
    }
    

    As the google api annotation proto mentions:

    Any fields in the request message which are not bound by the path template automatically become HTTP query parameters if there is no HTTP request body.

    So I suspect your message is getting zero values for the fields because there are no URL query parameters provided and the body annotation is missing.