Go-gRPC-Gateway

概念

gRPC-Gateway 是 google buffers 协议的编译器protoc的一个插件。

它读取gRPC服务定义并生成反向代理服务器,将gRPC转换成RESTful JSON APIs。

gRPC-Gateway 将发布 以 gRPC 和 RESTful 两种方式同时发布

运作过程:

  • 当 HTTP 请求到达 gRPC-Gateway 时,它会将 JSON 数据解析为 protobuf 消息。然后它使用解析的 protobuf 消息发出一个普通的 Go gRPC 客户端请求。
  • Go gRPC 客户端将 protobuf 结构编码成 protobuf 二进制格式并发送给 gRPC 服务器。gRPC 服务器处理请求并以 protobuf 二进制格式返回响应。
  • Go gRPC 客户端将其解析为 protobuf 消息并返回给 gRPC-Gateway,gRPC-Gateway 将 protobuf 消息编码为 JSON 并返回给原始客户端。

注意:如果不修改 proto 文件,可使用yaml配置文件来操作。

环境搭建

$ go install \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
    google.golang.org/protobuf/cmd/protoc-gen-go \
    google.golang.org/grpc/cmd/protoc-gen-go-grpc

有关protoc、gRPC的详细操作参考我另外几篇博客

Protoc3入门

Go gRPC入门

整体目录结构

proto文件定义

trip.proto

syntax = "proto3";
package coolcar;
option go_package="coolcar/protc/gen/go;trippb";


message Location {
    double latitude = 1;
    double longitude = 2;
}

enum TripStatus {
    TS_NOT_SPECIFIED = 0;
    NOT_STARTED = 1;
    IN_PROGRESS = 2;
    FINISHED = 3;
    PAID = 4;
}

message Trip {
    string start =1;
    string end =2;
    int64 duration_sec = 3;
    int64 fee_cent = 4;
    Location start_pos = 5;
    Location end_pos = 6;
    repeated Location path_locations = 7;
    TripStatus status =8;
    bool isPromotionTrip = 9;
    bool isFromGuestUser = 10;
}

message GetTripRequest {
    string id = 1;
}

message GetTripResponse {
    string id = 1;
    Trip trip = 2;
}

service TripService {
    rpc GetTrip (GetTripRequest) returns (GetTripResponse);
}

yaml文件定义

trip.yaml

type: google.api.Service
config_version: 3

http:
  rules:
    # coolcar.TripService.GetTrip --> selector定义:protoc定义的包名+service服务名+rpc方法名
    - selector: coolcar.TripService.GetTrip
      # 向外暴露的REST API风格接口
      get: /trip/{id}

shell文件

gen.sh

protoc -I=. --go-grpc_out=paths=source_relative:gen/go  --go_out=paths=source_relative:gen/go trip.proto
protoc -I=. --grpc-gateway_out=paths=source_relative,grpc_api_configuration=trip.yaml:gen/go trip.proto

在proto目录下执行./gen.sh生成proto文件

Service服务端定义

trip.go

package trip

import (
	"context"
	trippd "coolcar/proto/gen/go"
)

// Service
type Service struct {
	trippd.UnimplementedTripServiceServer
}

func (s *Service) GetTrip(c context.Context, req *trippd.GetTripRequest) (*trippd.GetTripResponse, error) {
	return &trippd.GetTripResponse{
		Id: req.Id,
		Trip: &trippd.Trip{
			Start:       "abc",
			End:         "",
			DurationSec: 3600,
			FeeCent:     10000,
			StartPos: &trippd.Location{
				Latitude:  30,
				Longitude: 120,
			},
			EndPos: &trippd.Location{
				Latitude:  35,
				Longitude: 115,
			},
			PathLocations: []*trippd.Location{
				{
					Latitude:  31,
					Longitude: 119,
				},
				{
					Latitude:  32,
					Longitude: 118,
				},
			},
			Status: trippd.TripStatus_IN_PROGRESS,
		},
	}, nil
}

main.go

package main

import (
	"context"
	trippd "coolcar/proto/gen/go"
	trip "coolcar/tripservice"
	"log"
	"net"
	"net/http"

	"google.golang.org/protobuf/encoding/protojson"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
)

const Addr = ":8081"

func main() {
	log.SetFlags(log.Lshortfile)
	go startGRPCGateway()
	s := grpc.NewServer()
	trippd.RegisterTripServiceServer(s, &trip.Service{})

	lis, err := net.Listen("tcp", Addr)
	if err != nil {
		log.Printf("failed to listen: %v", err)
	}

	log.Fatal(s.Serve(lis))
}

func startGRPCGateway() {
	c := context.Background()
	c, cancel := context.WithCancel(c)
	defer cancel()
	/**
	grpc-gatewayV1、V2版本不兼容
	EnumsAsInts -> MarshalOptions.UseEnumNumbers
	OrigName -> MarshalOptions.UseProtoNames
	DiscardUnknown -> UnmarshalOptions. DiscardUnknown
	*/
	mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
		MarshalOptions: protojson.MarshalOptions{
			UseProtoNames:  true, // 使用proto字段名代替JSON中的骆驼式名称的字段名。  
			UseEnumNumbers: true, // 使用protoc枚举定义的值作为数字发送。
		},
		UnmarshalOptions: protojson.UnmarshalOptions{
			DiscardUnknown: true, // 未知字段将被忽略
		},
	}))
	err := trippd.RegisterTripServiceHandlerFromEndpoint(
		c,
		mux,
		Addr,
		[]grpc.DialOption{grpc.WithInsecure()})
	if err != nil {
		log.Fatalf("connot start grpc gateway:%v", err)
	}
	err = http.ListenAndServe(":8080", mux)
	if err != nil {
		log.Fatalf("connot listen and server:%v", err)
	}
}

Client客户端定义

main.go

package main

import (
	"context"
	trippb "coolcar/proto/gen/go"
	"fmt"
	"log"

	"google.golang.org/grpc"
)

func main() {
	conn, err := grpc.Dial(":8081", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("cannot dial: %v", err)
	}
	tsClient := trippb.NewTripServiceClient(conn)
	r, err := tsClient.GetTrip(context.Background(), &trippb.GetTripRequest{Id: "trip456"})
	if err != nil {
		log.Fatalf("cannot call GetTrip: %v", err)
	}
	fmt.Println(r)
}

测试效果

$ curl http://localhost:8080/trip/trip123

{"id":"trip123","trip":{"start":"abc","duration_sec":3600,"fee_cent":10000,"start_pos":{"latitude":30,"longitude":120},"end_pos":{"latitude":35,"longitude":115},"path_locations":[{"latitude":31,"longitude":119},{"latitude":32,"longitude":118}],"status":2}}

参考:
https://github.com/grpc-ecosystem/grpc-gateway
https://grpc-ecosystem.github.io/grpc-gateway/

posted @ 2022-03-07 15:03  自己有自己的调调、  阅读(657)  评论(0编辑  收藏  举报