Golang处理gRPC请求/响应元数据的示例代码

 更新时间:2024年03月01日 09:30:04   作者:ghosind  
前段时间实现内部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.ServerTransportStream

  header  metadata.MD
  trailer metadata.MD
}

func (s *WrappedServerTransportStream) SendHeader(md metadata.MD) error {
  if err := s.ServerTransportStream.SendHeader(md); err != nil {
    return err
  }

  s.header = md

  return 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.ServerStream
  ctx 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.ServerStream

  header  metadata.MD
  trailer metadata.MD
}

func (s *WrappedStream) SendHeader(md metadata.MD) error {
  if err := s.ServerStream.SendHeader(md); err != nil {
    return err
  }

  s.header = md

  return 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
}

以上就是Golang处理gRPC请求/响应元数据的示例代码的详细内容,更多关于Golang处理gRPC请求/响应的资料请关注脚本之家其它相关文章!

相关文章

  • golang gin 监听rabbitmq队列无限消费的案例代码

    golang gin 监听rabbitmq队列无限消费的案例代码

    这篇文章主要介绍了golang gin 监听rabbitmq队列无限消费,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • go语言中线程池的实现

    go语言中线程池的实现

    Go语言中并没有直接类似 Java 线程池的内建概念,主要通过goroutine和channel来实现并发处理,本文主要介绍了go语言中线程池的实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-04-04
  • Golang中下划线(_)的不错用法分享

    Golang中下划线(_)的不错用法分享

    golang中的下划线表示忽略变量的意思,也没有产生新的变量,但是后面的表达式依然会被执行,本文为大家整理了golang中下划线的一些不错的用法,需要的可以参考下
    2023-05-05
  • Go正则表达式匹配字符串,替换字符串方式

    Go正则表达式匹配字符串,替换字符串方式

    介绍了Go语言中使用正则表达式进行字符串匹配和替换的方法,包括匹配单个子字符串和所有子字符串,个人经验分享,旨在为读者提供实用的编程技巧,并鼓励大家支持脚本之家
    2025-02-02
  • Go语言库系列之dotsql详解

    Go语言库系列之dotsql详解

    这篇文章主要介绍了Go语言库系列之dotsql的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • Go语言interface详解

    Go语言interface详解

    这篇文章主要介绍了Go语言interface详解,本文讲解了什么是interface、interface类型、interface值、空interface、interface函数参数等内容,需要的朋友可以参考下
    2014-10-10
  • 源码剖析Golang中map扩容底层的实现

    源码剖析Golang中map扩容底层的实现

    之前的文章详细介绍过Go切片和map的基本使用,以及切片的扩容机制。本文针对map的扩容,会从源码的角度全面的剖析一下map扩容的底层实现,需要的可以参考一下
    2023-03-03
  • go cron定时任务的基本使用讲解

    go cron定时任务的基本使用讲解

    这篇文章主要为大家介绍了gocron定时任务的基本使用讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • golang goquery selector选择器使用示例大全

    golang goquery selector选择器使用示例大全

    这篇文章主要为大家介绍了golang goquery selector选择器使用示例大全,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • go项目实现mysql接入及web api的操作方法

    go项目实现mysql接入及web api的操作方法

    这篇文章主要介绍了go项目实现mysql接入以及web api,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08

最新评论