gRPC之proto数据验证
1、proto数据验证
本篇将介绍grpc_validator
,它可以对gRPC数据的输入和输出进行验证。
这里复用上一篇文章的代码。
1.1 创建proto文件,添加验证规则
这里使用第三方插件go-proto-validators
自动生成验证规则。
地址:https://github.com/mwitkow/go-proto-validators
$ go get github.com/mwitkow/go-proto-validators
1.1.1 新建simple.proto文件
syntax = "proto3";
package proto;
option go_package = "./proto;proto";// validator.proto是github.com/mwitkow/go-proto-validators/validator.proto
import "validator.proto";message InnerMessage {// some_integer can only be in range (1, 100).int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];// some_float can only be in range (0;1).double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}message OuterMessage {// important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];// proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.InnerMessage inner = 2 [(validator.field) = {msg_exists: true}];
}service Simple {rpc Route (InnerMessage) returns (OuterMessage) {};
}
validator.proto
文件内容:
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.// Protocol Buffers extensions for defining auto-generateable validators for messages.// TODO(mwitkow): Add example.syntax = "proto2";
package validator;import "google/protobuf/descriptor.proto";option go_package = "github.com/mwitkow/go-proto-validators;validator";// TODO(mwitkow): Email protobuf-global-extension-registry@google.com to get an extension ID.extend google.protobuf.FieldOptions {optional FieldValidator field = 65020;
}extend google.protobuf.OneofOptions {optional OneofValidator oneof = 65021;
}message FieldValidator {// Uses a Golang RE2-syntax regex to match the field contents.optional string regex = 1;// Field value of integer strictly greater than this value.optional int64 int_gt = 2;// Field value of integer strictly smaller than this value.optional int64 int_lt = 3;// Used for nested message types, requires that the message type exists.optional bool msg_exists = 4;// Human error specifies a user-customizable error that is visible to the user.optional string human_error = 5;// Field value of double strictly greater than this value.// Note that this value can only take on a valid floating point// value. Use together with float_epsilon if you need something more specific.optional double float_gt = 6;// Field value of double strictly smaller than this value.// Note that this value can only take on a valid floating point// value. Use together with float_epsilon if you need something more specific.optional double float_lt = 7;// Field value of double describing the epsilon within which// any comparison should be considered to be true. For example,// when using float_gt = 0.35, using a float_epsilon of 0.05// would mean that any value above 0.30 is acceptable. It can be// thought of as a {float_value_condition} +- {float_epsilon}.// If unset, no correction for floating point inaccuracies in// comparisons will be attempted.optional double float_epsilon = 8;// Floating-point value compared to which the field content should be greater or equal.optional double float_gte = 9;// Floating-point value compared to which the field content should be smaller or equal.optional double float_lte = 10;// Used for string fields, requires the string to be not empty (i.e different from "").optional bool string_not_empty = 11;// Repeated field with at least this number of elements.optional int64 repeated_count_min = 12;// Repeated field with at most this number of elements.optional int64 repeated_count_max = 13;// Field value of length greater than this value.optional int64 length_gt = 14;// Field value of length smaller than this value.optional int64 length_lt = 15;// Field value of length strictly equal to this value.optional int64 length_eq = 16;// Requires that the value is in the enum.optional bool is_in_enum = 17;// Ensures that a string value is in UUID format.// uuid_ver specifies the valid UUID versions. Valid values are: 0-5.// If uuid_ver is 0 all UUID versions are accepted.optional int32 uuid_ver = 18;
}message OneofValidator {// Require that one of the oneof fields is set.optional bool required = 1;
}
1.1.2 编译simple.proto文件
$ go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
$ go install github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
$ protoc --govalidators_out=. --go_out=plugins=grpc:. simple.proto
编译完成后,自动生成simple.pb.go
和simple.validator.pb.go
文件,simple.pb.go
文件不再介绍,我们
看下simple.validator.pb.go
文件。
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: simple.protopackage protoimport (fmt "fmt"math "math"proto "github.com/golang/protobuf/proto"_ "github.com/mwitkow/go-proto-validators"regexp "regexp"github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators"
)// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inffunc (this *InnerMessage) Validate() error {if !(this.SomeInteger > 0) {return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be greater than '0'`, this.SomeInteger))}if !(this.SomeInteger < 100) {return github_com_mwitkow_go_proto_validators.FieldError("SomeInteger", fmt.Errorf(`value '%v' must be less than '100'`, this.SomeInteger))}if !(this.SomeFloat >= 0) {return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be greater than or equal to '0'`, this.SomeFloat))}if !(this.SomeFloat <= 1) {return github_com_mwitkow_go_proto_validators.FieldError("SomeFloat", fmt.Errorf(`value '%v' must be lower than or equal to '1'`, this.SomeFloat))}return nil
}var _regex_OuterMessage_ImportantString = regexp.MustCompile(`^[a-z]{2,5}$`)func (this *OuterMessage) Validate() error {if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) {return github_com_mwitkow_go_proto_validators.FieldError("ImportantString", fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`, this.ImportantString))}if nil == this.Inner {return github_com_mwitkow_go_proto_validators.FieldError("Inner", fmt.Errorf("message must exist"))}if this.Inner != nil {if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Inner); err != nil {return github_com_mwitkow_go_proto_validators.FieldError("Inner", err)}}return nil
}
里面自动生成了message
中属性的验证规则。
1.2 把grpc_validator验证拦截器添加到服务端
package mainimport ("context"grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"pb "middleware/proto""middleware/server/middleware/auth""middleware/server/middleware/cred""middleware/server/middleware/recovery""middleware/server/middleware/zap""google.golang.org/grpc""log""net"
)// SimpleService 定义我们的服务
type SimpleService struct{}// Route 实现Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.InnerMessage) (*pb.OuterMessage, error) {res := pb.OuterMessage{ImportantString: "hello grpc validator",Inner: req,}return &res, nil
}const (// Address 监听地址Address string = ":8000"// Network 网络通信协议Network string = "tcp"
)func main() {// 监听本地端口listener, err := net.Listen(Network, Address)if err != nil {log.Fatalf("net.Listen err: %v", err)}// 新建gRPC服务器实例grpcServer := grpc.NewServer(// TLS+Token认证cred.TLSInterceptor(),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(// 参数验证grpc_validator.StreamServerInterceptor(),// grpc_zap日志记录grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),// grpc_auth认证grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),// grpc_recovery恢复grpc_recovery.StreamServerInterceptor(recovery.RecoveryInterceptor()),)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(// 参数验证grpc_validator.UnaryServerInterceptor(),// grpc_zap日志记录grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),// grpc_auth认证grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),// grpc_recovery恢复grpc_recovery.UnaryServerInterceptor(recovery.RecoveryInterceptor()),)),)// 在gRPC服务器注册我们的服务pb.RegisterSimpleServer(grpcServer, &SimpleService{})log.Println(Address + " net.Listing with TLS and token...")//用服务器 Serve() 方法以及我们的端口信息区实现阻塞等待,直到进程被杀死或者 Stop() 被调用err = grpcServer.Serve(listener)if err != nil {log.Fatalf("grpcServer.Serve err: %v", err)}
}
[root@zsx middleware]# go run server.go
2023/02/11 21:42:01 :8000 net.Listing with TLS and token...
1.3 客户端
package mainimport ("context""middleware/token"pb "middleware/proto""google.golang.org/grpc""google.golang.org/grpc/credentials""log"
)// Address 连接地址
const Address string = ":8000"var grpcClient pb.SimpleClientfunc main() {//从输入的证书文件中为客户端构造TLS凭证creds, err := credentials.NewClientTLSFromFile("./cert/server/server.pem", "test.example.com")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}//构建Tokentoken := auth.Token{Value: "bearer grpc.auth.token",}// 连接服务器conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))if err != nil {log.Fatalf("net.Connect err: %v", err)}defer conn.Close()// 建立gRPC连接grpcClient = pb.NewSimpleClient(conn)route()
}// route 调用服务端Route方法
func route() {// 创建发送结构体req := pb.InnerMessage{SomeInteger: 99,SomeFloat: 1,}// 调用我们的服务(Route方法)// 同时传入了一个 context.Context ,在有需要时可以让我们改变RPC的行为,比如超时/取消一个正在运行的RPCres, err := grpcClient.Route(context.Background(), &req)if err != nil {log.Fatalf("Call Route err: %v", err)}// 打印返回值log.Println(res)
}
[root@zsx middleware]# go run client.go
2023/02/11 21:44:11 important_string:"hello grpc validator" inner:{some_integer:99 some_float:1}
运行后,当输入不符合要求的数据:
// 创建发送结构体
req := pb.InnerMessage{SomeInteger: 199,SomeFloat: 1,
}
验证失败后,会有以下错误返回:
[root@zsx middleware]# go run client.go
2023/02/11 21:45:25 Call Route err: rpc error: code = InvalidArgument desc = invalid field SomeInteger: value '199' must be less than '100'
exit status 1
# 项目结构
$ tree middleware/
middleware/
├── cert
│ ├── ca.crt
│ ├── ca.csr
│ ├── ca.key
│ ├── ca.srl
│ ├── client
│ │ ├── client.csr
│ │ ├── client.key
│ │ └── client.pem
│ ├── openssl.cnf
│ └── server
│ ├── server.csr
│ ├── server.key
│ └── server.pem
├── client.go
├── go.mod
├── go.sum
├── log
│ └── debug.log
├── proto
│ ├── simple.pb.go
│ └── simple.validator.pb.go
├── server
│ └── middleware
│ ├── auth
│ │ └── auth.go
│ ├── cred
│ │ └── cred.go
│ ├── recovery
│ │ └── recovery.go
│ └── zap
│ └── zap.go
├── server.go
├── simple.proto
├── token
│ └── token.go
└── validator.proto12 directories, 25 files
1.3 其他类型验证规则设置
enum
验证:
syntax = "proto3";
package proto;
option go_package = "./proto;enum";import "validator.proto";message SomeMsg {Action do = 1 [(validator.field) = {is_in_enum: true}];
}
enum Action {ALLOW = 0;DENY = 1;CHILL = 2;
}
UUID
验证:
syntax = "proto3";
package proto;
option go_package = "./proto;uuid";import "validator.proto";message UUIDMsg {// user_id must be a valid version 4 UUID.string user_id = 1 [(validator.field) = {uuid_ver: 4, string_not_empty: true}];
}
1.4 总结
go-grpc-middleware
中grpc_validator
集成go-proto-validators
,我们只需要在编写proto时设好验证规
则,并把grpc_validator
添加到gRPC服务端,就能完成gRPC的数据验证,很简单也很方便。
相关文章:
gRPC之proto数据验证
1、proto数据验证 本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证。 这里复用上一篇文章的代码。 1.1 创建proto文件,添加验证规则 这里使用第三方插件go-proto-validators 自动生成验证规则。 地址:https://github.co…...

计算机竞赛 题目: 基于深度学习的疲劳驾驶检测 深度学习
文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 🔥 优…...

css--踩坑
1. 子元素的宽高不生效问题 设置flex布局后,子元素的宽高不生效问题。 如果希望子元素的宽高生效,解决方法,给子元素添加如下属性: flex-shrink: 0; flex-shrink: 0;2. 横向滚动(子元素宽度不固定) /* tab…...

C超市商品信息查询系统
一、系统界面介绍 1. 超市商品信息查询系统 1、显示商品信息,包括:商品名称、商品种类(休闲食品、奶品水饮、生鲜水果)、商品价格、商品保质期、商品生产日期; 2、从文件中导入数据、显示、排序、查询的功能。&…...

黑马JVM总结(二十七)
(1)synchronized代码块 synchronized代码块的底层原理,它是给一个对象进行一个加锁操作,它是如何保证如果你出现了synchronized代码块中出现了问题,它需要给这个对象有一个正确的解锁操作呢,加锁解锁是成对…...
软件测试/测试开发丨Python异常处理 学习笔记
点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接:https://ceshiren.com/t/topic/27722 异常处理 编写程序时,即使语句或表达式使用了正确的语法,执行时仍可能触发错误。执行时检测到的错误称为异常,大…...

026 - STM32学习笔记 - 液晶屏控制(三) - DMA2D快速绘制矩形、直线
026- STM32学习笔记 - 液晶屏控制(三) - DMA2D快速绘制矩形、直线等 上节直接操作LTDC在先视频上直接显示,我们直接操作显存地址空间中的内容,用来显示图形,但是相对来说,这种方法费时费力,这节…...

【牛客网】OR59 字符串中找出连续最长的数字串
题目 思路 创建两个字符串 temp 和 ret 创建指针i用来遍历字符串通过i遍历字符串,如果遇到数字则将这个数组加到字符串temp中 i,如果遇到字母,则判断temp字符串的长度和ret字符串的长度,如果temp<ret则说明这个字符串不是要的字符串,如果temp>ret则说明此时temp字符串是…...

云原生监控系统Prometheus:基于Prometheus构建智能化监控告警系统
目录 一、理论 1.Promethues简介 2.监控告警系统设计思路 3.Prometheus监控体系 4.Prometheus时间序列数据 5.Prometheus的生态组件 6.Prometheus工作原理 7.Prometheus监控内容 8.部署Prometheus 9.部署Exporters 10.部署Grafana进行展示 二、实验 1.部署Prometh…...

C++ 学习系列 -- std::list
一 std::list 介绍 list 是 c 中的序列式容器,其实现是双向链表,每个元素都有两个指针,分别指向前一个节点与后一个节点 链表与数组都是计算机常用的内存数据结构,与数组连续内存空间不一样的地方在于,链表的空间是不…...
YOLOv8血细胞检测(6):多维协作注意模块MCA | 原创独家创新首发
💡💡💡本文改进:多维协作注意模块MCA,效果秒杀ECA、SRM、CBAM,创新性十足,可直接作为创新点使用。 MCA | 亲测在血细胞检测项目中涨点,map@0.5 从原始0.895提升至0.910 收录专栏: 💡💡💡YOLO医学影像检测:http://t.csdnimg.cn/N4zBP ✨✨✨实战医学影…...
FFmpeg横竖版视频互换背景模糊一键生成
视频处理是现代多媒体应用中常见的需求。其中横竖版视频互换和背景模糊是视频编辑中常见的操作。FFmpeg是一个功能强大的工具,适用于这些任务。 本文将详细介绍如何使用FFmpeg进行横竖版视频互换和背景模糊。 文章目录 操作命令与命令说明横版转竖版竖版转横版背景模糊处理横…...

Java 华为真题-小朋友分班
需求: 题目描述 幼儿园两个班的小朋友在排队时混在了一起,每位小朋友都知道自己是否与前面一位小朋友同班,请你帮忙把同班的小朋友找出来小朋友的编号是整数,与前一位小朋友同班用Y表示,不同班用N表示学生序号范围(0&…...

机器学习必修课 - 编码分类变量 encoding categorical variables
1. 数据预处理和数据集分割 import pandas as pd from sklearn.model_selection import train_test_split导入所需的Python库 !git clone https://github.com/JeffereyWu/Housing-prices-data.git下载数据集 # Read the data X pd.read_csv(/content/Housing-prices-data/t…...

ClickHouse进阶(二十二):clickhouse管理与运维-服务监控
进入正文前,感谢宝子们订阅专题、点赞、评论、收藏!关注IT贫道,获取高质量博客内容! 🏡个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Kerberos安全认证-CSDN博客 📌订阅:拥抱独家专题,你的订阅将点燃我的创作热情! 👍点赞:赞同优秀创作,你的点赞是对我创…...

Hadoop使用hdfs指令查看hdfs目录的根目录显示被拒
背景 分布式部署hadoop,服务机只有namenode节点,主机包含其他所有节点 主机关机后,没有停止所有节点,导致服务机namenode继续保存 再次开启主机hadoop,使用hdfs查看hdfs根目录的时候显示访问被拒 解决方案 1.主机再次开启hadoop并继续执行关闭 2.服务器再次开启hadoop并继…...
[Mac] 安装paddle-pipelines出现 ERROR: Failed building wheel for lmdb
今天在mac换了新系统,然后重新安装paddle-piplines的时候出现了下面的问题: xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrunerror: comma…...
LED灯亮灭
.text .global _start _start: 设置GPIO寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28LDR R0,0x50000A28LDR R1,[R0] 从R0为起始地址的4个字节数据取出放入R1中ORR R1,R1,#(0x1<<4) 第四位设置为1STR R1,[R0] 写回LDR R0,0x5000…...
Acwing.143 最大异或对(trie树)
题目 在给定的N个整数A1,A2 . …Ax中选出两个进行xor(异或)运算,得到的结果最大是多少? 输入格式 第一行输入一个整数N。 第二行输入N个整数A1~AN。 输出格式 输出一个整数表示答案。 数据范围 1 ≤N ≤105,0≤A<231 输入样例: 3 1 2 3输出样…...

day10.8ubentu流水灯
流水灯 .text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28LDR R0,0X50000A28LDR R1,[R0] 从r0为起始地址的4字节数据取出放在R1ORR R1,R1,#(0x1<<4) 第4位设置为1STR R1,[R0] 写回2.设置PE10管脚为输出模式 G…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...