Golang处理gRPC请求/响应元数据

前段时间实现内部gRPC框架时,为了实现在服务端拦截器中打印请求及响应的头部信息,便查阅了部分关于元数据的资料。因为中文网络上对于该领域的信息较少,于是在这做了一些简单的总结。

元数据

gRPC的元数据(metadata)是基于HTTP/2头部实现的键值对数据,它通常用来实现gRPC的鉴权、链路跟踪以及自定义头部数据等功能。

gRPC的元数据分为两种类型,分别是HeaderTrailerHeader可以由客户端或服务端发送,它在客户端请求数据或服务器响应数据前发送。Trailer是一种特殊的头部信息,它仅可由服务端发送,且位于发送的数据之后。

客户端处理

在gRPC客户端中,无论是一元调用还是流调用,可以比较简单地通过google.golang.org/grpc/metadata包提供的AppendToOutgoingContextNewOutgoingContext方法向请求中加入头部元数据,例如以下几种方式:

// 通过metadata创建新的context
md := metadata.Pairs("k1", "v1", "k2", "v2")
ctx := metadata.NewOutgoingContext(ctx, md)// 或是向context中添加元数据
ctx = metadata.AppendToOutgoingContext(ctx, "k3", "v3")// ... 通过ctx进行RPC调用

对于服务端返回的响应中的元数据,一元调用与流调用的处理方式就较为不同。对于一元调用,需要提前定义好用于存储元数据的变量,然后在调用时通过grpc.Headergrpc.Trailer增加调用的选项:

var header, trailer metadata.MD
resp, err := cli.UnaryCall(ctx, req, grpc.Header(&header), grpc.Trailer(&trailer))// 处理header或trailer

而对于任意方式的流调用,都可以简单地通过流调用返回流的HeaderTrailer方法获得元数据:

stream, err := cli.StreamCall(ctx)header, err := stream.Header()
trailer, err := stream.Trailer()

服务端处理

对于服务端,请求的元数据需要通过metadata.FromIncomingContext从context中获取:

// 一元调用
md, ok := metadata.FromIncomingContext(ctx)// 流调用
ctx := stream.Context() // 需要先从流中得到context
md, ok := metadata.FromIncomingContext(ctx)

同样,在服务端发送元数据需要根据一元调用与流调用使用不同的方式。对于一元调用,可以通过grpc.SendHeadergrpc.SetHeader以及grpc.SetTrailer方法设置发送的元数据,例如:

header := metadata.Pairs("header-key", "header-val")
grpc.SendHeader(ctx, header)
trailer := metadata.Pairs("trailer-key", "trailer-val")
grpc.SetTrailer(ctx, trailer)

对于上述的SendHeaderSetHeader方法,其区别为SendHeader方法只能调用一次,而SetHeader方法将会对所有调用的元数据进行合并发送。

对于流调用,服务端发送元数据则是通过流对象中的上述方法:

header := metadata.Pairs("header-key", "header-val")
stream.SendHeader(,header)
trailer := metadata.Pairs("trailer-key", "trailer-val")
stream.SetTrailer(trailer)

服务器拦截器处理

对于gRPC服务端一元调用及流调用拦截器,请求元数据的读取与响应元数据的发送与上一节中的实现相同,便不再赘述。下面我们将讨论一下在拦截器中更新请求元数据,以及读取响应的元数据。

一元调用拦截器更新请求元数据

在服务端拦截器中更新请求的元数据,其实现的方式与客户端发送元数据类似,即需要通过更新后的元数据创建新的context。对于一元调用拦截器,其简单实现如下所示:

md, ok := metadata.FromIncomingContext(ctx)md.Append("new-key", "new-value")
ctx = metadata.NewIncomingContext(ctx, md)resp, err := handler(ctx, req) // 传递context至handler中

一元调用拦截器读取响应元数据

对于一元调用响应的元数据,gRPC未提供直接访问的方法响应的元数据。为了在拦截器中能读取到响应的元数据,我们可以通过覆盖原始grpc.ServerTransportStream并对设置的元数据进行备份的方式进行实现。

type WrappedServerTransportStream struct {grpc.ServerTransportStreamheader  metadata.MDtrailer metadata.MD
}func (s *WrappedServerTransportStream) SendHeader(md metadata.MD) error {if err := s.ServerTransportStream.SendHeader(md); err != nil {return err}s.header = mdreturn nil
}// 在需要的情况下继续实现下面的几个方法:
// func (s *WrappedServerTransportStream) SetHeader(metadata.MD) error
// func (s *WrappedServerTransportStream) SetTrailer(metadata.MD) error

在定义带有元数据副本的ServerTransportStream实现后,我们需要通过grpc.ServerTransportStreamFromContext获取到一元调用的原始流,在对其进行封装后,调用grpc.NewContextWithServerTransportStream创建新的context。

stream := grpc.ServerTransportStreamFromContext(ctx)
wrappedStream := &WrappedServerTransportStream{ServerTransportStream: stream,
}
ctx = grpc.NewContextWithServerTransportStream(ctx, wrappedStream)resp, err := handler(ctx, req)// 通过wrappedStream.header、wrappedStream.trailer读取响应的元数据

需要注意,grpc.ServerTransportStream接口是一个实验性的接口,在后续版本中可能会被移除,所以本节中描述的方法在后续版本中可能不再可用。

流调用拦截器更新请求元数据

而对于流调用,gRPC没有提供修改其context的方法,为了实现修改流调用请求元数据,就需要实现grpc.ServerStream接口并加入带有修改后元数据的context。以下是一个简单的实现:

type WrappedStream struct {grpc.ServerStreamctx context.Context
}func (s *WrappedStream) Context() context.Context {return s.ctx
}func ExampleStreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {md, ok := metadata.FromIncomingContext(ss.Context())md.append("new-key", "new-value")ctx := metadata.NewIncomingContext(ss.Context(), md)return handler(srv, &WrappedStream{ss, ctx})
}

流调用拦截器读取响应元数据

与在一元调用拦截器中相同,若需要在流调用拦截器中读取响应的元数据,我们可以实现grpc.ServerStream接口,并在其中保存元数据的副本。例如我们可以在上节的WrappedStream的基础上,对其进行一定修改:

type WrappedStream struct {grpc.ServerStreamheader  metadata.MDtrailer metadata.MD
}func (s *WrappedStream) SendHeader(md metadata.MD) error {if err := s.ServerStream.SendHeader(md); err != nil {return err}s.header = mdreturn nil
}// 继续实现SetHeader、SetTrailer等方法func ExampleStreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {stream := &WrappedStream{ServerStream: ss}err := handler(srv, stream)// 通过stream.header、stream.trailer读取响应元数据return err
}

参考资料

  • Metadata - gRPC Guides
  • gRPC Metadata Go Documentation
  • Metadata interceptor example
  • ServerInterceptor how to get header/trailer data - GitHub Issues

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/712719.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java通过Semaphore控制同一时间只有3个线程运行

怎么控制同一时间只有3个线程运行? 直接上代码 import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore;public class SemaphoreThreadPoolExample {private static…

【网站项目】324企业人事信息管理系统

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

Windows下卸载JDK

操作步骤: 直接到windows程序卸载面板进行卸载 然后删除已配置的环境变量

深入解析Golang的encoding/ascii85库:从基础到实战

深入解析Golang的encoding/ascii85库:从基础到实战 引言基础知识什么是ASCII85编码?ASCII85编码的工作原理ASCII85编码的优点ASCII85编码的缺点 使用Golang的encoding/ascii85库引入encoding/ascii85包ASCII85编码ASCII85解码实战示例小结 进阶技巧和最佳…

用于游戏开发的顶级 PYTHON 框架

一、说明 我们试图用python开发游戏,一旦产生这个念头,就伴随这样一个问题:当今用于构建游戏的领先 Python 框架有哪些?python下,支持游戏开发平台有哪些优势?我们在这篇博文中告诉你。 二、高级游戏平台简…

Python 深拷贝和浅拷贝

Python 深拷贝和浅拷贝 Python 深拷贝和浅拷贝 Python 深拷贝和浅拷贝 在Python中,对象赋值有深拷贝和浅拷贝之分。 浅拷贝(Shallow Copy):创建一个新的对象,但它包含的是对原始对象中包含项的引用,而不是…

驱动高级--mknod

一、起源 仅devfs,导致开发不方便以及一些功能难以支持: 热插拔 不支持一些针对所有设备的统一操作(如电源管理) 不能自动mknod 用户查看不了设备信息 设备信息硬编码,导致驱动代码通用性差,即没有分离…

【改进算法】【IHAOAVOA】天鹰优化算法和非洲秃鹫混合优化算法

目录 1 主要内容 IHAOAVOA流程图 主要创新点 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现《IHAOAVOA: An improved hybrid aquila optimizer and African vultures optimization algorithm for global optimization problems》,天鹰优化算法&am…

spring boot3解决跨域的几种方式

⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途。 目录 1.前言 2.何为跨域 3.跨域问题出现特征 4.方式一:使用 CrossOrigin 注解 5.方式二:自定义…

2024年3月2日 十二生肖 今日运势

小运播报:2024年3月2日,星期六,农历正月廿二 (甲辰年丙寅月乙丑日),法定节假日。 红榜生肖:鸡、蛇、鼠 需要注意:狗、马、羊 喜神方位:西北方 财神方位:东…

微服务间通信重构与服务治理笔记

父工程 依赖版本管理,但实际不引入依赖 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…

力扣111 二叉树的最小深度 Java版本

文章目录 题目描述层序遍历解法递归解法 题目描述 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,nu…

Java中finally语句中的代码在什么情况下不会被执行?

在Java中&#xff0c;finally块中的代码几乎在任何情况下都会执行&#xff0c;无论是在try块中的代码正常执行完毕&#xff0c;还是遇到异常被catch块捕获时。finally块主要用于执行清理工作&#xff0c;比如释放资源等。然而&#xff0c;存在少数几种情况下finally块中的代码不…

[SS]语义分割_膨胀卷积

膨胀卷积 目录 一、概念 1、定义 2、知识点 二、详细介绍 1、引入 2、膨胀系数设定 一、概念 1、定义 膨胀卷积&#xff08;Dilated Convolution&#xff09;&#xff0c;也称为空洞卷积&#xff08;Atrous Convolution&#xff09;&#xff0c;是一种在卷积神经网络…

MySQLBackup备份数据库

环境&#xff1a;MySQL 8.0.36 1、安装部署 [rootnode5 ~]# wget -c https://edelivery.oracle.com/osdc/softwareDownload?fileNameV1040085-01.zip [rootnode5 ~]# ll total 22776 -rw-------. 1 root root 1066 Jan 21 14:59 anaconda-ks.cfg -r–r–r–. 1 root root 23…

Cloudera虚拟机配置(虚拟机环境自带Hadoop、Impala等大数据处理应用)

上学期的大数据处理课程&#xff0c;笔者被分配到Impala的汇报主题。然而汇报内容如果单纯只介绍Impala的理论知识&#xff0c;实在是有些太过肤浅&#xff0c;最起码得有一些实际操作来展示一下Impala的功能。但是Impala的配置实在是有些困难与繁琐&#xff0c;于是笔者通过各…

uni-app去除页面头部的标题栏

uniapp项目 每个界面都会有一个标题栏 配置在我们项目根目录的 pages.json中 我们将它全部去掉 上面还是有一条黑的 体验非常差 我们只需要在pages.json中 指定page的 style中加入 "navigationStyle": "custom"对应的page 就没有这个标题栏了

【王道数据结构】【chapter8排序】【P371t6】

试设计一个算法&#xff0c;判断一个数据序列是否构成一个小根堆&#xff08;下面代码中的堆排序的部分仅仅是为了方便设计测试用例&#xff09; #include <iostream> #include<time.h> #include<stdlib.h>int * buildarray(int size) {int* tmp(int *) mall…

蓝桥杯备赛第四篇(高级数据结构)

1.树状数组 public static int getSum(int i) {int sum 0;for (int j i; j > 1; j - lowbit(j)) {sum tree[j];}return sum;}public static void update(int i, int update) {for (int j i; j < n; j lowbit(j)) {tree[j] update;}}public static int lowbit(int n…

00x集体——cad 中DB(database)对象—— vba实现

Database 对象 外部参照块的内容。 VBA 类名 AcadDatabase 创建方法 不可使用 访问途径 Block.XRefDatabase 该对象提供对外部参照块的访问。只有IsXRef属性为TRUE的块才适用。 方法 CopyObjects 属性 Application Blocks Dictionaries DimStyles Ele…