当前位置: 代码网 > it编程>前端脚本>Golang > Golang处理gRPC请求/响应元数据的示例代码

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

2024年05月19日 Golang 我要评论
元数据grpc的元数据(metadata)是基于http/2头部实现的键值对数据,它通常用来实现grpc的鉴权、链路跟踪以及自定义头部数据等功能。grpc的元数据分为两种类型,分别是header及tr

元数据

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请求/响应的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com