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的详细操作参考我另外几篇博客
整体目录结构
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/