首页 > 其他分享 >gRPC 高级——Validator 验证器

gRPC 高级——Validator 验证器

时间:2024-07-14 18:30:46浏览次数:22  
标签:string err proto gRPC 验证 rules Validator go validate

gRPC 验证器(Validator)是一种用于在 gRPC 通信过程中进行数据验证的工具,通过在 .proto 文件中定义验证规则(例如长度限制、格式检查等),确保客户端和服务器之间传递的数据符合预期的格式和约束条件。它使用 Protocol Buffers 作为序列化机制,并通过生成的代码在传输过程中自动执行这些验证规则,从而提高系统的可靠性和安全性。

这一小节我们利用开源gRPC验证器项目protoc-gen-validate来实现gRPC在客户端与服务端之间的通信。

1. 安装protoc-gen-validate

# fetches this repo into $GOPATH
go get -d github.com/envoyproxy/protoc-gen-validate

2. protoc-gen-validate可支持的校验类型

类型校验规则
字符串min_len(最小长度)、max_len(最大长度)、length(固定长度)、pattern(正则表达式模式)、prefix(前缀)、suffix(后缀)、contains(包含)、in(在集合中)、not_in(不在集合中)、email(电子邮件)、hostname(主机名)、ip(IP地址)、ipv4(IPv4地址)、ipv6(IPv6地址)、uri(URI)、uri_ref(URI引用)、address(地址)、uuid(UUID)、well_known_regex(已知正则表达式)、well_known_regex_pattern(已知正则表达式模式)
Int32const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
Int64const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
UInt32const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
UInt64const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
SInt32const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
SInt64const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
Fixed32const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
Fixed64const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
SFixed32const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
SFixed64const(常量)、lt(小于)、lte(小于等于)、gt(大于)、gte(大于等于)、in(在集合中)、not_in(不在集合中)、ignore_empty(忽略空值)
布尔const(常量)
字节min_len(最小长度)、max_len(最大长度)、length(固定长度)、pattern(正则表达式模式)、prefix(前缀)、suffix(后缀)、contains(包含)、in(在集合中)、not_in(不在集合中)

3. protoc-gen-validate的语法

以部分string语法为例,详见请见官方文档

// x must be set to "foo"
string x = 1 [(validate.rules).string.const = "foo"];

// x must be exactly 5 characters long
string x = 1 [(validate.rules).string.len = 5];

// x must be at least 3 characters long
string x = 1 [(validate.rules).string.min_len = 3];

// x must be between 5 and 10 characters, inclusive
string x = 1 [(validate.rules).string = {min_len: 5, max_len: 10}];

// x must be at most 15 bytes long
string x = 1 [(validate.rules).string.max_bytes = 15];

// x must be between 128 and 1024 bytes long
string x = 1 [(validate.rules).string = {min_bytes: 128, max_bytes: 1024}];

// x must be a non-empty, case-insensitive hexadecimal string
string x = 1 [(validate.rules).string.pattern = "(?i)^[0-9a-f]+$"];

// x must begin with "foo"
string x = 1 [(validate.rules).string.prefix = "foo"];

// x must end with "bar"
string x = 1 [(validate.rules).string.suffix = "bar"];

// x must contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.contains = "baz"];

// x cannot contain "baz" anywhere inside it
string x = 1 [(validate.rules).string.not_contains = "baz"];

// x must be a valid email address (via RFC 5322)
string x = 1 [(validate.rules).string.email = true];

// x must be a valid address (IP or Hostname).
string x = 1 [(validate.rules).string.address = true];

// x must be a valid hostname (via RFC 1034)
string x = 1 [(validate.rules).string.hostname = true];

4. 使用示例

0. 目录树如下
.
├── client
│   └── client.go
├── proto
│   ├── student_grpc.pb.go
│   ├── student.pb.go
│   ├── student.pb.validate.go
│   ├── student.proto
│   └── validate.proto
└── server
    └── server.go

1. 建立proto/student.proto

代码如下:

syntax = "proto3";

import "validate.proto";

option go_package = ".;proto";

service StudentService {
  rpc GetStudent(Student) returns (Student);
}

message Student {
  // uid 是学生的唯一标识符,它必须大于999
  uint64 uid = 1 [(validate.rules).uint64.gt = 999];

  // email 是学生的电子邮件地址,它必须是一个有效的电子邮件地址
  string email = 2 [(validate.rules).string.email = true];

  // phone 是学生的电话号码,它必须符合中国大陆的手机号码格式
  string phone = 3 [(validate.rules).string = {pattern: "^1[3456789]\\d{9}$"}];
}

此时import语句会爆红,显示Cannot resolve import 'validate.proto',所以我们需要建立validate.proto文件

2. 建立proto/validate.proto

使用protoc-gen-validate需要我们自己cv代码或者用import语句引入它的validate.proto文件。这里我们以用cv大法自己建立validate.proto文件为例。

建立proto/validate.proto后,代码可以从两种方式进行cv。

  1. 在GO Modules内找到 github.com/envoyproxy/[email protected]这个库中的validate/validate.proto文件,利用cv大法复制到proto/validate.proto文件中。
  2. 从GitHub找到此项目该文件的代码:validate/validate.proto,利用cv大法复制到proto/validate.proto文件中。

在解决掉proto/validate.proto问题后。我们利用生成命令生成对应的go文件。

protoc student.proto --go_out=. --go-grpc_out=. --validate_out="lang=go:."
3.编写server/server.go代码
package main

import (
	"context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"net"

	"google.golang.org/grpc"

	"go-try/grpc_validate/proto"
)

type StudentServer struct {
	proto.UnimplementedStudentServiceServer
}

// Validate接口定义了一个需要被实现的方法Validate。
// 任何类型只要实现了Validate方法(返回一个error)就满足了这个接口。
// 在这个demo中,它被用于在运行时检查请求是否可以进行验证。
type Validate interface {
	Validate() error
}

func (s *StudentServer) GetStudent(ctx context.Context, request *proto.Student) (*proto.Student, error) {
	return &proto.Student{
		Uid:   1,
		Email: "[email protected]",
		Phone: "12345678912",
	}, nil
}

// 拦截器的函数
var interceptor = func(ctx context.Context, req any,
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
	// 尝试将请求转换为Validate接口类型
	if r, ok := req.(Validate); ok {
		// 如果转换成功,调用Validate方法进行验证
		if err := r.Validate(); err != nil {
			// 如果验证失败,返回一个错误状态
			return nil, status.Error(codes.InvalidArgument, err.Error())
		}
	}
	return handler(ctx, req)
}

func main() {
	var opts []grpc.ServerOption
	opts = append(opts, grpc.UnaryInterceptor(interceptor))

	server := grpc.NewServer(opts...)
	proto.RegisterStudentServiceServer(server, &StudentServer{})
	listen, err := net.Listen("tcp", "0.0.0.0:8080")
	if err != nil {
		panic("faild to listen : " + err.Error())
	}
	err = server.Serve(listen)
	if err != nil {
		panic("faild to start grpc : " + err.Error())
	}
}

4.编写client/client.go代码
package main

import (
	"context"
	"fmt"

	"google.golang.org/grpc"

	"go-try/grpc_validate/proto"
)

func main() {
	conn, err := grpc.NewClient("0.0.0.0:8080", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	cli := proto.NewStudentServiceClient(conn)
	msg, err := cli.GetStudent(context.Background(), &proto.Student{Uid: 1234}) 
    // 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Uid: value must be less than 999
	
    msg, err := cli.GetStudent(context.Background(), &proto.Student{Email: "1234"})
    // 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Email: value must be a valid email address | caused by: mail: missing '@' or angle-addr
	
    msg, err := cli.GetStudent(context.Background(), &proto.Student{Phone: "1234"})
    // 报错信息:panic: rpc error: code = InvalidArgument desc = invalid Student.Email: value must be a valid email address | caused by: mail: no address
    
	msg, err := cli.GetStudent(context.Background(), &proto.Student{
		Uid:   1,
		Email: "[email protected]",
		Phone: "19536486248",
	})
    // 运行成功!运行结果:uid:1 email:"[email protected]" phone:"12345678912"
    
	if err != nil {
		panic(err)
	}
	fmt.Println(msg)
}

5.分别运行server.go和client.go

运行结果在client代码内已标明,至此,一个gRPC验证器简单的demo项目已经完成,相信你已经学会了如何使用gRPC验证器。

标签:string,err,proto,gRPC,验证,rules,Validator,go,validate
From: https://blog.csdn.net/MPY_3/article/details/140420543

相关文章

  • springboot+vue前后端分离项目-项目搭建10-验证码功能
    本次演示的验证码功能只涉及前端,而且功能简单1.新建vue/src/components/ValidCode.vue<template><divclass="ValidCodedisabled-select":style="`width:${width};height:${height}`"@click="refreshCode"><spanv-f......
  • gRPC高级——Metadata机制
    什么是MetadatagRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。metadata使得client和s......
  • gRPC 高级——Interceptor 拦截器
    gRPC拦截器是一种用于在RPC方法调用的生命周期中拦截和处理请求和响应的机制。拦截器允许开发者在请求到达实际服务方法之前或在响应返回客户端之前执行自定义逻辑。它们类似于中间件,广泛应用于日志记录、身份验证、请求修改等场景。拦截器的种类客户端拦截器(ClientInter......
  • 温湿度传感器的学习及基于串口和逻辑分析仪的验证
    目录温湿度传感器温湿度传感器的基本概述测量精度硬件接线时序分析接收数据分析代码全局变量配置DHT11为输出模式配置DHT11为输入模式配置初始化DHT11发送开始信号判断DHT11温湿度传感器是否应答接收一位数据接收一个字节的数据接收DHT11传输的数据串口USART1的初始化利用串口发送......
  • LeetCode 125. 验证回文串
    更多题解尽在https://sugar.matrixlab.dev/algorithm每日更新。组队打卡,更多解法等你一起来参与哦!LeetCode125.验证回文串,难度简单。双指针解题思路:遍历字符串,将所有大写字符转换为小写字符、并移除所有非字母数字字符;使用左右指针比较字符,出现不同则直接返回fa......
  • [LeetCode]953. 验证外星语词典
    /*953.验证外星语词典已解答简单某种外星语也使用英文小写字母,但可能顺序order不同。字母表的顺序(order)是一些小写字母的排列。给定一组用外星语书写的单词words,以及其字母表的顺序order,只有当给定的单词在这种外星语中按字典序排列时,返回true;否则,返回false。示例1:......
  • 易优CMS后台登陆验证码怎么关闭(图文)
    可以安装易优助手插件,在插件里可以直接关闭验证码。插件内置多种功能,包含功能有:1.检测数据库2.文档付费限制3.文档链接提取4.文档管理器5.特殊字符6.检查bom头部信息7.执行数据库8.检测目录权限9.sql命令执行10.病毒扫描11.木马图片扫描12.检测重复文档13.重置数据表ID14.后台......
  • Hibernate-validator校验框架
    转载:http://blog.csdn.net/xgblog/article/details/525486591前言Validator开发者使用手册,适用后台校验功能的开发参考。1.1. 背景在我们日常后台的开发中,涉及到很多的字段验证,一般普通的验证框架就基本上可以满足日常的开发需求,但有一些情况,这些普通的验证框架无法达到要求......
  • protobuf-net.Grpc 笔记
    众所周知,Grpc很好用,但每次都需要手动编写*.proto文件,protobuf-net.Grpc个人感觉最大的优势是不用写*.proto文件,相关教程如下:https://learn.microsoft.com/zh-cn/aspnet/core/grpc/code-first?view=aspnetcore-8.0https://protobuf-net.github.io/protobuf-net.Grpc/gettingst......
  • 研究gRPC所给的helloworld例子
    这里我以编写一个远程过程调用,客户端传过来请求,远程过程调用就可以返回当前时间。(daytime服务器熟知端口是13,这里并不是搭建daytime,只是为了测试远程过程调用是否成功)CMakeLists.txt文件的编写cmake_minimum_required(VERSION3.8)project(HelloWorldCCXX)include(../cmake/......