protocol 和 grpc 的基本使用
protocol
标量数值类型
protocol buffer
类型对应各语言的类型:Scalar Value Types
int32
对于负值的效率很低,应该使用 sint32
默认值
string
默认是空字符串bytes
默认是空切片bool
默认是 false
0
0
enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; }
- 如果如果要将不同的枚举常量定义相同的值,需要设置
allow_alias
为true
enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. }
option go_package 作用
option go_package
用来指明生成的包名,语法为:option go_package = ";";
比如:
"./;proto"
,当前目录下,包名为proto
"common/stream/proto/v1"
,如果这些路径不存在会创建这些路径,包名为v1
"common/stream/proto/v1;proto"
,路径为:common/stream/proto/v1
,包名为proto
在一个 proto 文件中引用另一个 proto 文件
使用 import
关键词,语法为:import "";
可以引入第三方的 proto
文件,也可以引入自己的 proto
文件
base.proto
文件内容如下:
syntax = "proto3"; option go_package = "./;proto"; message Empty {} message Pong { string id = 1; }
在自己的 proto
文件中引入 base.proto
文件:
import "base.proto"; service Greeter { rpc Ping(Empty) returns(Pong); }
引入第三方的 proto
文件:
- 要注意的一点是:在使用
protoc
生成代码时,需要指定proto_path
参数
import "google/protobuf/empty.proto"; service Greeter { rpc Ping(google.protobuf.Empty) returns(Pong); } message Pong { string id = 1; }
对于第三方在 client/server
中如何使用呢?
比如上面使用的 google.protobuf.Empty
,在生成的 go
文件中找到 Empty
,然后再看引入它的路径,将它复制到你的 client/server
中即可。
嵌套 message 对象
嵌套使用,Address
是 Person
的嵌套对象,Address
只能在 Person
中使用,不能在 Person
外使用
message Person { string name = 1; int32 age = 2; Address address = 3; message Address { string country = 1; string city = 2; } }
在 client/server
中怎么使用呢?
还是去源码中找 address
,我们会看到生成的 address
是 Person_Address
,使用就可以了
ps:通过
import
引入自己写的proto
文件,要自己单独生成go
文件,然后再使用,否则会找不到
map 类型
message Person { string name = 1; int32 age = 2; map address = 3; }
在 client/server
中使用
c.Ping(context.Background(), &proto.Person{ Name: "uccs", Age: 18, Address: map[string]string{ "City": "深圳", }, })
使用 Timestamp
import "google/protobuf/timestamp.proto"; message Person { string name = 1; int32 age = 2; map address = 3; google.protobuf.Timestamp birthday = 4; }
在 client/server
中如何使用 timestamp
呢?
也是一样去生成的 go
文件中找,如图所示:
找到它后,如何实例化 timestamppb
呢?
还是一步步去追溯源码,找到 New
方法,然后再看它的参数,就可以了
import "google.golang.org/protobuf/types/known/timestamppb" c.Ping(context.Background(), &proto.Person{ Name: "uccs", Age: 18, Address: map[string]string{ "City": "深圳", }, Birthday: timestamppb.New(time.Now()), })
grpc
grpc 的 metadata
对于每次请求,需要 grpc
带上一些 metadata
,用来传递一些额外的信息,比如 token
,trace id
等等。
metadata
是以 key-value
的形式存在的,key
为 string
类型,value
为 []string
类型。
metadata
文档和源码
metadata
类型定义如下:
type MD map[string][]string
实例化 metadata
实例化 metadata
有两种方式:
第一种:
md := metadata.New(map[string][]string{"key1": "value1", "key2": "value2"})
第二种:这种写法不区分大小写,会统一转成小写;key
和 value
之间也是用逗号分隔
md := metadata.Pairs( "key1", "value1", "key1", "value1-1", // 会自动组合成 { key1: 【value1, value1-1} "key2", "value2", )
发送 metadata
md := metadata.Pairs("key1", "value1") // 新建一个有 metadata 的 context ctx := metadata.NewOutgoingContext(context.Background(), md) // 单向 rpc res, err := client.SomeMethod(ctx, SomeRequest)
接收 metadata
SomeMethod(ctx context.Context, in *pb.SomeRequest)(*pb.SomeResponse, error){ md, ok := metadata.FromIncomingContext(ctx) }
拦截器
请请求或者响应之前,统一对他们进行一些验证或处理,以实现一些通用的功能
在 server 中使用拦截器
server
中使用 UnaryInterceptor
,它返回一个 grpc.ServerOption
,可以在 grpc.NewServer
中使用
opt := grpc.UnaryInterceptor(interceptor)
我们只需要实现 interceptor
函数即可,它的签名通过查看 grpc.UnaryInterceptor
可以知道:
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
具体代码如下:
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { fmt.Println("server 端被拦截了...") return handler(ctx, req) } opt := grpc.UnaryInterceptor(interceptor) g := grpc.NewServer(opt)
在 client 中使用拦截器
client
中使用 WithUnaryInterceptor
,它返回一个 grpc.DialOption
,可以在 grpc.Dial
中使用
opt := grpc.WithUnaryInterceptor(interceptor)
我们只需要实现 interceptor
函数即可,它的签名通过查看 grpc.WithUnaryInterceptor
可以知道:
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
具体代码如下:
interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { fmt.Println("client 端被拦截了...") return invoker(ctx, method, req, reply, cc, opts...) } opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), opt)
应用
以验证 token
为例:
在 client
端修改 ctx
即可:
interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { md := metadata.New(map[string]string{ "token": "uccs", }) ctx = metadata.NewOutgoingContext(ctx, md) return invoker(ctx, method, req, reply, cc, opts...) } opt := grpc.WithUnaryInterceptor(interceptor) conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), opt)
server
端验证 token
:
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return resp, status.Errorf(codes.Unauthenticated, "无 token") } var ( token string ) if v1, ok := md["token"]; ok { token = key1[0] } if token != "10101" { return resp, status.Errorf(codes.Unauthenticated, "无效的 token") } return handler(ctx, req) } opt := grpc.UnaryInterceptor(interceptor) g := grpc.NewServer(opt)
使用 gprc 改写这个方法
grpc
提供了一个 WithPerRPCCredentials
拦截器,可以在 client
端使用,它的签名如下:
type PerRPCCredentials interface { GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) RequireTransportSecurity() bool // 返回 `true` 表示开启 `TLS`,返回 `false` 表示不开启 `TLS` }
也就是说我们实现 GetRequestMetadata
和 RequireTransportSecurity
方法即可实现拦截器
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "token": "101011", }, nil } func (c customCredential) RequireTransportSecurity() bool { return false } opt := grpc.WithPerRPCCredentials(customCredential{}) conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), opt)
异常处理
状态码文档
server 端
server
抛出错误:
return status.Error(codes.InvalidArgument, "invalid username")
client
处理错误
st, ok := status.FromError(err) if ok { st.Message() st.Code() }