前文
关于grpc是什么以及python如何搭建,可以参考这篇:https://blog.csdn.net/weixin_42681866/article/details/121509556?spm=1001.2014.3001.5501,本篇说下如何基于golang来搭建;基本上grpc在golang的应用是非常广泛的,主要是用于agent和master的通讯,同时也是云原生的通讯必备;比如k8s就是用的grpc。
实操
当我们定义好proto文件的时候,需要通过golang-tools将其转换为go文件(这里通过protoc来当转译工具),来支持项目内使用。
golang protoc工具安装:
- 进入protobuf release添加链接描述 页面,选择适合自己操作系统的压缩包文件
- 解压protoc-3.14.0-osx-x86_64.zip并进入protoc-3.14.0-osx-x86_64
$ cd protoc-3.14.0-osx-x86_64/bin
- 将启动的protoc二进制文件移动到被添加到环境变量的任意path下,如$GOPATH/bin,这里不建议直接将其和系统的一下path放在一起。
$ mv protoc $GOPATH/bin
- 通过上述命令,将protoc绑定到命令行工具,才能进行编译
当工具安装完后,需要安装对应包
生成proto的包:go get -u github.com/golang/protobuf/protoc-gen-go
项目内需要用到的grpc:go get -u google.golang.org/grpc
生成文件命令:
protoc -I. --go_out=plugins=grpc:./pb pb/helloworld.proto
注意:如果执行的时候,报路径无法解析的话,则需要在proto文件里手动指定路径,增加:
// 指定 go 的包路径及包名
option go_package="/batch;tasks";
搭建客户端和服务端
网上相关的很多,这里简单提一下,主要是介绍
搭建服务端:
package server
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
"net"
)
type rpcServer struct{}
// 占位
func (s *rpcServer) Hello(ctx context.Context, req *helloWorldPb.HelloRequest) (*helloWorldPb.HelloReply, error) {
// 做权限校验
if err := checkSecret(ctx); err != nil {
return &helloWorldPb.HelloReply{}, err
}
return &helloWorldPb.HelloReply{}, nil
}
func RunServer() {
lis, err := net.Listen("tcp", "127.0.0.1")
if err != nil {
logger.Log.Fatalf("failed to listen: %v", err)
}
// 创建 RPC 服务容器; 这里的filter是过滤器,失败后不阻塞
s := grpc.NewServer(grpc.UnaryInterceptor(filter))
helloWorldPb.RegisterBatchRouteServer(s, &rpcServer{}) // 在gRPC服务端注册服务
reflection.Register(s) //注册服务器反射服务
// 每个grpc连接对应着gorouting,当请求进来对应分配来处理
err = s.Serve(lis)
if err != nil {
logger.Log.Fatalf("failed to serve: %v", err)
}
}
这里附上权限校验和过滤器:
// grpc过滤器捕获异常,不使服务停止
func filter(
ctx context.Context, req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
logger.Log.Println("filter:", info)
defer func() {
if r := recover(); r != nil {
logger.Log.Errorf("panic: %v", r)
}
}()
return handler(ctx, req)
}
// grpc 统一校验函数
func checkSecret(ctx context.Context) error {
var (
appID string
appSecret string
)
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
var authError exception.AuthError
return status.Errorf(codes.Unauthenticated, authError.Error())
}
if value, ok := md["app_id"]; ok {
appID = value[0]
}
if value, ok := md["app_secret"]; ok {
appSecret = value[0]
}
if appID != config.Conf.AppId || appSecret != config.Conf.AppSecret {
var authError exception.AuthError
return status.Errorf(codes.Unauthenticated, authError.Error())
}
return nil
}
这里插一句,对应到python的grpc校验,就是直接在stub.HelloWorld(hello_request, metadata=self.metadata)的形式将app_id和app_secret传入即可。
golang客户端加权限认证:
package client
import (
"context"
"google.golang.org/grpc"
)
type Client struct {
ServerUrl string
}
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
// customCredential 自定义认证
type customCredential struct{}
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"app_id": Conf.AppId,
"app_secret": Conf.AppSecret,
}, nil
}
func (c customCredential) RequireTransportSecurity() bool {
return false
}
func (c *Client) initClient() (*grpc.ClientConn, error) {
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
//opts = append(opts, grpc.WithBlock()) // 这个参数开启则当目标服务器连接不上的时候,会阻塞而不是立刻返回失败
// 使用自定义认证
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
conn, err := grpc.Dial(c.ServerUrl, opts...)
return conn, err
}
// 接下来根据initClient返回的连接,结合pb提供的函数可完成客户端的请求