关于go语言中protobuf高级使用

如果你仅仅是想玩玩go-web单机开发可以参考gin项目框架

一、微服务之间传递数据

  • 1、在微服务中不仅仅是可以通过入参和返回参数来进行数据交互,另外还可以通过metadata的方式传递参数

  • 2、定义一个简单的proto文件

    syntax = "proto3";
    
    option go_package = ".;proto";
    
    service HelloWorld {
      rpc SayHello(HelloRequest) returns(HelloResponse);
    }
    
    message HelloRequest {
      string name = 1;
    }
    
    message HelloResponse {
      string message = 1;
    }
    
  • 3、服务器端定义接口客户端传递过来的metadata的数据

    type server struct {
    }
    
    func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
    	md, ok := metadata.FromIncomingContext(ctx)
    	if !ok {
    		log.Fatalf("获取metadata数据失败")
    	}
    	if nameSlice, ok := md["name"]; ok {
    		fmt.Println("获取到的数据", nameSlice[0])
    	}
    	return &proto.HelloResponse{
    		Message: "hello" + in.Name,
    	}, nil
    }
    func main() {
    	listen, err := net.Listen("tcp", ":9000")
    	if err != nil {
    		log.Fatalf("监听端口错误:" + err.Error())
    	}
    	service := grpc.NewServer()
    	proto.RegisterHelloWorldServer(service, &server{})
    	err = service.Serve(listen)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    }
    
  • 4、客户端定义metadata传递参数给服务器端

    func main() {
    	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	defer conn.Close()
    
    	c := proto.NewHelloWorldClient(conn)
    	// 自定义传递的metadata参数
    	md := metadata.New(map[string]string{
    		"name":     "admin",
    		"password": "123456",
    	})
    	ctx := metadata.NewOutgoingContext(context.Background(), md)
    	response, err := c.SayHello(ctx, &proto.HelloRequest{
    		Name: "admin",
    	})
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	fmt.Println("服务器端返回的数据" + response.Message)
    }
    

二、拦截器的使用interceptor

  • 1、所谓的拦截器是在网络请求的过程中,拦截请求和响应的,以下先配置一个简单的protobuf的网络请求,然后在这个基础上添加拦截器,来拦截客户端发送的数据到服务器端,也可以用来统计接口访问时长

    • 1、proto文件
    syntax = "proto3";
    
    option go_package = ".;proto";
    
    service HelloWorld {
      rpc SayHello(HelloRequest) returns(HelloResponse);
    }
    
    message HelloRequest {
      string name = 1;
    }
    
    message HelloResponse {
      string message = 1;
    }
    
    • 2、使用命令生成go文件
    protoc -I=. --go_out=plugins=grpc,paths=source_relative:. helloWorld.proto
    
    • 3、服务器端的代码

      type server struct {
      }
      
      func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
      	return &proto.HelloResponse{
      		Message: "hello" + in.Name,
      	}, nil
      }
      
      func main() {
      	listen, err := net.Listen("tcp", ":9000")
      	if err != nil {
      		log.Fatalf("监听端口错误:" + err.Error())
      	}
      	service := grpc.NewServer()
      	proto.RegisterHelloWorldServer(service, &server{})
      	err = service.Serve(listen)
      	if err != nil {
      		fmt.Println(err.Error())
      	}
      }
      
    • 4、客户端

      func main() {
      	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
      	if err != nil {
      		fmt.Println(err.Error())
      		return
      	}
      	defer conn.Close()
      
      	c := proto.NewHelloWorldClient(conn)
      	response, err := c.SayHello(context.Background(), &proto.HelloRequest{
      		Name: "admin",
      	})
      	if err != nil {
      		fmt.Println(err.Error())
      		return
      	}
      	fmt.Println("服务器端返回的数据" + response.Message)
      }
      
      
  • 2、在服务器端定义拦截器来拦截请求,客户端的不变,启动客户端和服务器端,观察服务器端

    func main() {
    	listen, err := net.Listen("tcp", ":9000")
    	if err != nil {
    		log.Fatalf("监听端口错误:" + err.Error())
    	}
    	// 定义拦截器
    	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    		fmt.Println("接收到一个新的请求")
    		return handler(ctx, req)
    	}
    	// 使用拦截器
    	opt := grpc.UnaryInterceptor(interceptor)
    	service := grpc.NewServer(opt)
    	
    	proto.RegisterHelloWorldServer(service, &server{})
    	err = service.Serve(listen)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    }
    
  • 3、拦截在请求前和请求后都打印信息出来

    ...
    // 定义拦截器
    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
    }
    // 使用拦截器
    opt := grpc.UnaryInterceptor(interceptor)
    service := grpc.NewServer(opt)
    ...
    
  • 4、客户端拦截器的使用

    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()
    		err := invoker(ctx, method, req, reply, cc, opts...)
    		fmt.Printf("耗时:%s\n", time.Since(start))
    		return err
    	}
    	// 使用拦截器
    	opt := grpc.WithUnaryInterceptor(interceptor)
    	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt)
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	defer conn.Close()
    
    	c := proto.NewHelloWorldClient(conn)
    	response, err := c.SayHello(context.Background(), &proto.HelloRequest{
    		Name: "admin",
    	})
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	fmt.Println("服务器端返回的数据" + response.Message)
    }
    

三、使用拦截器和metadata来做微服务之间的授权

  • 1、这里授权我们可以简单的做一个类似用户登录后才能访问的功能,把客户端想象成浏览器,把服务器端想象成之前我们熟悉的gin-web开发

  • 2、简单的demo和上面的一样的,只是在基础上添加拦截器和metadata的操作

  • 3、在客户端的拦截器中传递用户名和密码到服务器端

    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()
    		fmt.Printf("耗时:%s\n", time.Since(start))
    		// 使用metadata传递数据
    		md := metadata.New(map[string]string{
    			"username": "admin",
    			"password": "123456",
    		})
    		ctx = metadata.NewOutgoingContext(context.Background(), md)
    		err := invoker(ctx, method, req, reply, cc, opts...)
    		return err
    	}
    	// 使用拦截器
    	opt := grpc.WithUnaryInterceptor(interceptor)
    	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), opt)
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	defer conn.Close()
    
    	c := proto.NewHelloWorldClient(conn)
    	response, err := c.SayHello(context.Background(), &proto.HelloRequest{
    		Name: "admin",
    	})
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	fmt.Println("服务器端返回的数据" + response.Message)
    }
    
  • 4、服务器端在拦截中获取到metadata中的数据并且判断用户名和密码

    func main() {
    	listen, err := net.Listen("tcp", ":9000")
    	if err != nil {
    		log.Fatalf("监听端口错误:" + err.Error())
    	}
    	// 定义拦截器
    	interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    		fmt.Println("请求前打印")
    		// 解析metadata中的数据验证
    		md, ok := metadata.FromIncomingContext(ctx)
    		if !ok {
    			return resp, status.Error(codes.Unauthenticated, "无效的参数")
    		}
    		var (
    			username string
    			password string
    		)
    		if val, ok := md["username"]; ok {
    			username = val[0]
    		}
    		if val, ok := md["password"]; ok {
    			password = val[0]
    		}
    		fmt.Println(username, password, "接收到的参数")
    		if username != "admin" || password != "123456" {
    			return resp, status.Error(codes.Unauthenticated, "用户名和密码错误")
    		}
    		res, err := handler(ctx, req)
    		fmt.Println("请求完成打印")
    		return res, err
    	}
    	// 使用拦截器
    	opt := grpc.UnaryInterceptor(interceptor)
    	service := grpc.NewServer(opt)
    
    	proto.RegisterHelloWorldServer(service, &server{})
    	err = service.Serve(listen)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    }
    

四、参数校验

五、返回状态吗

  • 1、我们在http请求的时候有状态吗,自然在grpc的时候也有状态码,来标注当前请求成功与失败

  • 2、github参考文档

  • 3、服务器端使用状态码返回给客户端端

    import (
    	...
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    	"log"
    	"net"
    )
    
    type server struct {
    }
    
    func (s *server) SayHello(ctx context.Context, in *proto.HelloRequest) (*proto.HelloResponse, error) {
    	//return &proto.HelloResponse{
    	//	Message: "hello" + in.Name,
    	//}, nil
    	// 返回错误
    	return nil, status.Error(codes.NotFound, "记录没有找到")
    }
    
    func main() {
    	listen, err := net.Listen("tcp", ":9000")
    	if err != nil {
    		log.Fatalf("监听端口错误:" + err.Error())
    	}
    
    	service := grpc.NewServer()
    
    	proto.RegisterHelloWorldServer(service, &server{})
    	err = service.Serve(listen)
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    }
    
  • 4、客户端接收错误,根据错误中的状态码提示

    func main() {
    
    	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
    	if err != nil {
    		fmt.Println(err.Error())
    		return
    	}
    	defer conn.Close()
    
    	c := proto.NewHelloWorldClient(conn)
    	response, err := c.SayHello(context.Background(), &proto.HelloRequest{
    		Name: "admin",
    	})
    	if err != nil {
    		str, ok := status.FromError(err)
    		if !ok {
    			panic("解析错误信息失败")
    		}
    		fmt.Println(str.Message(), "获取到的错误信息")
    		fmt.Println(str.Code(), "获取到的错误code")
    		fmt.Println(err.Error())
    		return
    	}
    	fmt.Println("服务器端返回的数据" + response.Message)
    }
    

六、超时处理

  • 1、在客户端设置一个最大时间限制

    // 设置时间
    ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
    response, err := c.SayHello(ctx, &proto.HelloRequest{
      Name: "admin",
    })
    
  • 2、服务器端休眠下返回数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水痕01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值