Featured image of post Building APIs Quickly with go-zero and gRPC Gateway

Building APIs Quickly with go-zero and gRPC Gateway

Gateway Setup

  • Official documentation provides a basic introduction. Here we’ll cover more advanced implementation details.

Advanced Configuration

  • Customizing request paths and methods
  1. hello.protobuf file
syntax = "proto3";

package hello;
option go_package = "./hello";

// Add import
import "google/api/annotations.proto";

message Request {
}
message Response {
  string msg = 1;
}

service Hello {
  rpc Ping(Request) returns(Response) {
    // Add HTTP option
    option (google.api.http) = {
      post: "/api/v1/hello"
      body: "*"
    };
  }
}
  1. Update dependencies
# Manually download annotations.proto from: 
# https://github.com/googleapis/googleapis/tree/master/google/api
-app
   -hello.proto
   -google
	-api
            -annotations.proto
            -http.proto
	-protobuf
            -descriptor.proto
  1. Generate code
goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server
  • Custom errors and request headers
package main

import (
	"flag"

	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/gateway"
)

var configFile = flag.String("f", "etc/gateway.yaml", "config file")

func main() {
	
	flag.Parse()

	var c gateway.GatewayConf
	conf.MustLoad(*configFile, &c)
	gw := gateway.MustNewServer(c, withHeaders())
	defer gw.Stop()

	// Register custom error handler
	httpx.SetErrorHandlerCtx(grpcErrorHandlerCtx)

	gw.Start()
}

// Process headers (remove Grpc-Metadata- prefix)
func withHeaders() func(*gateway.Server) {
	return gateway.WithHeaderProcessor(func(header http.Header) []string {

		var values []string
		// Use protobuf enum names as keys
		for key := range xxx.MetaDataRequiredKey_value {
			values = append(values, fmt.Sprintf("%s:%s", key, header.Get(key)))
		}
		return values
	})
}

// Custom error response
func grpcErrorHandler(err error) (int, any) {
	if s, ok := status.FromError(err); ok {
		return http.StatusOK, Res{
			Code: int(s.Code()),
			Msg:  s.Message(),
		}
	}

	return http.StatusOK, Res{
		Code: http.StatusInternalServerError,
		Msg:  err.Error(),
	}
}

func grpcErrorHandlerCtx(ctx context.Context, err error) (int, any) {
	return grpcErrorHandler(err)
}

type Res struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

More

  • Default protobuf generation doesn’t include HTTP method/path definitions. A PR adds default POST method and RPC method name as API path.
  • Gateway headers require Grpc-Metadata- prefix when passed to RPC services.