protobuf
官方文档
基本数据类型
.proto Type | Notes | Go Type |
---|---|---|
double | float64 | |
float | float32 | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int32 |
uint32 | 使用变长编码 | uint32 |
uint64 | 使用变长编码 | uint64 |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int32 |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int64 |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | uint32 |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | uint64 |
sfixed32 | 总是4个字节 | int32 |
sfixed64 | 总是8个字节 | int64 |
bool | bool | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | string |
bytes | 可能包含任意顺序的字节数据。 | []byte |
- 在java中,无符号32位和64位整型被表示成他们的整型对应形似,最高位被储存在标志位中。
- 对于所有的情况,设定值会执行类型检查以确保此值是有效。
- 64位或者无符号32位整型在解码时被表示成为ilong,但是在设置时可以使用int型值设定,在所有的情况下,值必须符合其设置其类型的要求。
- python中string被表示成在解码时表示成unicode。但是一个ASCIIstring可以被表示成str类型。
- Integer在64位的机器上使用,string在32位机器上使用
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
● 对于strings,默认是一个空string
● 对于bytes,默认是一个空的bytes
● 对于bools,默认是false
● 对于数值类型,默认是0
● 对于枚举,默认是第一个定义的枚举值,必须为0;
● 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
对于可重复域的默认值是空(通常情况下是对应语言中空列表)。
注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。
查看generated code guide选择你的语言的默认值的工作细节。
proto文件中引入另一个proto文件
1.被引用的proto要先转成go代码
2.使用protobuf提供的proto引用格式与自定义的proto引用格式不同
base.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto";
message Empty{}
message Pong{
string id=1;
}
转成go代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative base.proto
hello.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto";
import "base.proto";
import "google/protobuf/empty.proto";
service Greeter{
rpc SayHello(HelloRquest)returns(HelloReply);//hello接口
rpc Ping(google.protobuf.Empty)returns (Pong);
}
message HelloRquest{
string name=1;//1是编号不是值
string url=2;
}
message HelloReply{
string message=1;
}
转成go代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
message嵌套
目录结构
hello.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto";
import "base.proto";
import "google/protobuf/empty.proto";
service Greeter{
rpc SayHello(HelloRquest)returns(HelloReply);//hello接口
rpc Ping(google.protobuf.Empty)returns (Pong);
}
message HelloRquest{
string name=1;//1是编号不是值
string url=2;
}
message HelloReply{
string message=1;
repeated Result data=2;
message Result{
string name=1;
string url=2;
}
}
转换
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
使用
package main
import "GolangStudy/Introduction/grpc/example4/proto"
func main() {
_ = proto.HelloReply_Result{}
}
枚举类型
目录结构
hello2.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto1";
service Greeter{
rpc SayHello(HelloRquest)returns(HelloReply);//hello接口
}
enum Gender{
MALE=0;
FEMALE=1;
}
message HelloRquest{
string name=1;//1是编号不是值
Gender g=3;
}
message HelloReply{
string message=1;
}
转换
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello2.proto
调用
package main
import (
_ "GolangStudy/Introduction/grpc/example4/proto"
"GolangStudy/Introduction/grpc/example4/proto1"
"context"
"fmt"
"google.golang.org/grpc"
)
func main() {
// _ = proto.HelloReply_Result{}
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto1.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto1.HelloRquest{
Name: "bobby",
G: proto1.Gender_FEMALE,
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
map类型
hello2.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto1";
service Greeter{
rpc SayHello(HelloRquest)returns(HelloReply);//hello接口
}
enum Gender{
MALE=0;
FEMALE=1;
}
message HelloRquest{
string name=1;//1是编号不是值
Gender g=2;
map<string,string> mp=3;
}
message HelloReply{
string message=1;
}
转换
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello2.proto
使用
package main
import (
_ "GolangStudy/Introduction/grpc/example4/proto"
"GolangStudy/Introduction/grpc/example4/proto1"
"context"
"fmt"
"google.golang.org/grpc"
)
func main() {
// _ = proto.HelloReply_Result{}
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto1.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto1.HelloRquest{
Name: "bobby",
G: proto1.Gender_FEMALE,
Mp: map[string]string{
"name": "bobby",
"company": "mooc",
},
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
timestamp类型
hello2.proto
syntax = "proto3";
// 生成proto文件所在包路径
package protos;
// 影响go文件生成位置和包名
//.是当前文件夹
option go_package = ".;proto1";
import "google/protobuf/timestamp.proto";
service Greeter{
rpc SayHello(HelloRquest)returns(HelloReply);//hello接口
}
enum Gender{
MALE=0;
FEMALE=1;
}
message HelloRquest{
string name=1;//1是编号不是值
Gender g=2;
map<string,string> mp=3;
google.protobuf.Timestamp addTime=4;
}
message HelloReply{
string message=1;
}
转换
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello2.proto
使用
package main
import (
_ "GolangStudy/Introduction/grpc/example4/proto"
"GolangStudy/Introduction/grpc/example4/proto1"
"context"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
)
func main() {
// _ = proto.HelloReply_Result{}
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto1.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto1.HelloRquest{
Name: "bobby",
G: proto1.Gender_FEMALE,
Mp: map[string]string{
"name": "bobby",
"company": "mooc",
},
AddTime: timestamppb.New(time.Now()),
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
grpc
metadata
grpc让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次rpc调用。
实例化
//第一种方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
//第二种方式 key不区分大小写,会被统一转成小写。
md := metadata.Pairs(
"key1", "val1",
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2",
)
使用
//发送
md := metadata.Pairs("key", "val")
// 新建一个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)
//接收
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
使用例子
拦截器
grpc Go支持“拦截器”,即在将请求传递到用户的应用程序逻辑之前在 grpc服务器上执行的中间件,或者在用户调用时在 grpc 客户端上执行的中间件。它是实现常见模式的完美方式:身份验证、日志记录、跟踪、指标、验证、重试、速率限制等,它们可以成为出色的通用构建块,让您轻松构建多个微服务。
实例
目录结构
hello.proto
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
//将 sessionid放入 放入cookie中 http协议
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
生成
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello2.proto
server
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"GolangStudy/Introduction/grpc/interpretor/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 继续处理请求
fmt.Println("接收到新请求")
res, err := handler(ctx, req)
fmt.Println("请求处理完成")
return res, err
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client
package main
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"GolangStudy/Introduction/grpc/interpretor/proto"
)
func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
fmt.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err)
return err
}
func main() {
//stream
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
// 指定客户端interceptor
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial("localhost:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
拦截器框架
auth认证
hello.proto
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
生成
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello2.proto
server
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"GolangStudy/Introduction/grpc/token_auth/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 继续处理请求
fmt.Println("接收到新请求")
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
var (
appid string
appkey string
)
if va1, ok := md["appid"]; ok {
appid = va1[0]
}
if va1, ok := md["appkey"]; ok {
appkey = va1[0]
}
fmt.Println(appid, appkey)
if appid != "101010" || appkey != "i am key" {
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
res, err := handler(ctx, req)
fmt.Println("请求处理完成")
return res, err
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client
package main
import (
"context"
"fmt"
"GolangStudy/Introduction/grpc/token_auth/proto"
"google.golang.org/grpc"
)
type customCredential struct{}
func (cc *customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
// RequireTransportSecurity indicates whether the credentials requires
// transport security.
func (cc *customCredential) RequireTransportSecurity() bool {
return false
}
func main() {
// interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// start := time.Now()
// md := metadata.New(map[string]string{
// "appid": "101010",
// "appkey": "i am key",
// })
// ctx = metadata.NewOutgoingContext(context.Background(), md)
// err := invoker(ctx, method, req, reply, cc, opts...)
// fmt.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err)
// return err
// }
//stream
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
// 指定客户端interceptor
opts = append(opts, grpc.WithPerRPCCredentials(&customCredential{}))
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
验证器
实例
Protocol Buffer Validation
使用的mac电脑,使用官网的第二种第三种都试过了不可以,第二种在$GOPATH:bin找不到protoc-gen-validate文件,第三种能找到但是将proto转换成go文件时会一直报如下错误,最后使用了第一种才成功。
protoc-gen-validate: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--validate_out: protoc-gen-validate: Plugin failed with status code 1.
//第一种
go install github.com/envoyproxy/protoc-gen-validate@latest
//第二种
go get -d github.com/envoyproxy/protoc-gen-validate
//第三种
git clone https://github.com/bufbuild/protoc-gen-validate.git
cd $GOPATH/bin
cd protoc-gen-validate
make build
hello.proto
syntax = "proto3";
option go_package=".;proto";
import "validate.proto";
service Greeter {
rpc SayHello (Person) returns (Person);
}
message Person {
uint64 id = 1 [(validate.rules).uint64.gt = 999];
string email = 2 [(validate.rules).string.email = true];
string mobile = 3 [(validate.rules).string = {
pattern: "^1[3456789]\\d{9}$"}];
}
转换
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --validate_out="lang=go:." hello.proto
client
package main
import (
"GolangStudy/Introduction/grpc/validate/proto"
"context"
"fmt"
"google.golang.org/grpc"
)
type customCredential struct{}
func main() {
var opts []grpc.DialOption
//opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial("localhost:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
//rsp, _ := c.Search(context.Background(), &empty.Empty{})
rsp, err := c.SayHello(context.Background(), &proto.Person{
Id: 9999,
Email: "bobby@qq.com",
Mobile: "19999999999",
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Id)
}
server
package main
import (
"context"
"net"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc"
"GolangStudy/Introduction/grpc/validate/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person,
error) {
return &proto.Person{
Id: 32,
}, nil
}
type Validator interface {
Validate() error
}
func main() {
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 继续处理请求
if r, ok := req.(Validator); ok {
if err := r.Validate(); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}