更改connect-go拦截器中的响应体
来源:stackoverflow
时间:2024-02-20 16:06:25 409浏览 收藏
你在学习Golang相关的知识吗?本文《更改connect-go拦截器中的响应体》,主要介绍的内容就涉及到,如果你想提升自己的开发能力,就不要错过这篇文章,大家要知道编程理论基础和实战操作都是不可或缺的哦!
我正在使用 buf 的 connect-go 库来实现 grpc 服务器。
许多 grpc 调用都是时间敏感的,因此它们包含一个客户端用来发送其当前时间戳的字段。服务器将客户端时间戳与本地时间戳进行比较并返回它们之间的差值。以下是 .proto
定义的示例:
service eventservice { // start performing a task rpc start (startrequest) returns (startresponse); } message startrequest { int64 location_id = 1; int64 task_id = 2; location user_latlng = 3; google.protobuf.timestamp now_on_device = 4; } message startresponse { taskperformanceinfo info = 1; google.protobuf.duration device_offset = 2; }
因为我已经为多个 rpc 方法实现了此功能,所以我想看看是否可以使用拦截器来处理它,这样我就不需要确保它在所有单独的 rpc 方法实现中得到处理。 p>
由于 protoc-gen-go
编译器如何定义字段的 getter,因此通过定义接口并使用类型断言可以轻松检查请求消息是否包含 now_on_device
字段:
type hasnowondevice interface { getnowondevice() *timestamppb.timestamp }
if reqwithnow, ok := req.any().(hasnowondevice); ok { // ... }
这使得大部分拦截器都非常容易编写:
func makedevicetimeinterceptor() func(connect.unaryfunc) connect.unaryfunc { return connect.unaryinterceptorfunc( func(next connect.unaryfunc) connect.unaryfunc { return connect.unaryfunc(func(ctx context.context, req connect.anyrequest) (connect.anyresponse, error) { now := time.now().utc() ctxa := context.withvalue(ctx, currenttimestampkey{}, now) var devicetimeoffset time.duration // if the protobuf message has a `nowondevice` field, use it // to get the difference betweent the device time and server time. if reqwithnow, ok := req.any().(hasnowondevice); ok { devicetime := reqwithnow.getnowondevice().astime() devicetimeoffset = now.sub(devicetime) ctxa = context.withvalue(ctxa, devicetimediffkey{}, devicetimeoffset) } res, err := next(ctxa, req) // todo: how do i modify the response here? return res, err }) }, ) }
我遇到的问题(如上面的评论中所述)是如何修改响应。
我无法像为请求定义接口一样为响应定义接口,因为 protoc-gen-go
没有定义 setter。然后我想我可以使用类型开关,如下所示(其中 todo
注释位于上面):
switch resMsg := res.Any().(type) { case *livev1.StartResponse: resMsg.DeviceOffset = durationpb.New(deviceTimeOffset) return &connect.Response[livev1.StartResponse]{ Msg: resMsg, }, err case *livev1.StatusResponse: resMsg.DeviceOffset = durationpb.New(deviceTimeOffset) return &connect.Response[livev1.StatusResponse]{ Msg: resMsg, }, err }
这种方法存在三个问题:
- 我找不到将旧响应中的标头/预告片复制到新响应中的方法。 (我认为此时它们实际上尚未设置,但我不确定。)
- 使用类型断言需要我为每种类型一遍又一遍地重复几乎相同的代码块。
- 这不再比在每个 rpc 方法中单独实现它更简单。
是否有更简单的方法来使用拦截器来修改响应中的字段?或者我应该采取其他方式吗?
正确答案
deepankar 概述了一种解决方案,但我确实看到了将所有响应数据保留在架构定义的响应结构中的吸引力。如果 protoc-gen-go
生成 setter 与 getter 一起使用,这肯定会更简单!
我找不到将旧响应中的标题/预告片复制到新响应中的方法。 (我认为此时它们实际上尚未设置,但我不确定。)
你不需要这样做。在您的示例中, res.any()
返回指向 protobuf 消息的指针 - 您可以就地修改它。您的类型开关可能如下所示:
switch resmsg := res.any().(type) { case *livev1.startresponse: resmsg.deviceoffset = durationpb.new(devicetimeoffset) case *livev1.statusresponse: resmsg.deviceoffset = durationpb.new(devicetimeoffset) } return res, err
使用类型断言需要我为每种类型一遍又一遍地重复几乎相同的代码块。
不幸的是,你最好的选择可能是反思。您可以在标准 go 反射或 protobuf 反射之间进行选择 - 两者都可以。使用 protobuf 反射,类似这样的事情应该可以解决问题:
res, err := next(ctx, req) if err != nil { return nil, err } msg, ok := res.any().(proto.message) if !ok { return res, nil } // keep your logic to calculate offset! var devicetimeoffset time.duration // you could make this a global. durationname := (*durationpb.duration)(nil).protoreflect().descriptor().fullname() refmsg := msg.protoreflect() offsetfd := refmsg.descriptor().fields().byname("deviceoffset") if offsetfd != nil && offsetfd.message() != nil && offsetfd.message().fullname() == durationname { refoffset := durationpb.new(devicetimeoffset).protoreflect() refmsg.set( offsetfd, protoreflect.valueof(refoffset), ) } return res, nil
您认为这比重复类型开关好还是坏取决于您 - 它要复杂一些,但它确实让事情变得更干燥。
您有可能吗使用标题而不是正文。如果客户端可以通过请求标头发送 nowondevice
,那么您可以改为在响应标头中发回响应。 unix 时间戳可能是最好的方法。
func makedevicetimeinterceptor() connect.unaryinterceptorfunc { return func(next connect.unaryfunc) connect.unaryfunc { return func(ctx context.context, req connect.anyrequest) (connect.anyresponse, error) { now := time.now().utc() ctxa := context.withvalue(ctx, currenttimestampkey{}, now) var devicetimeoffset time.duration // check the header message `now-on-device` field, instead of body reqwithnow := req.header().get("now-on-device") if reqwithnow != "" { val, err := strconv.atoi(reqwithnow) if err != nil { return nil, connect.newerror(connect.codeinvalidargument, errors.new("invalid timestamp")) } devicetime := time.unix(int64(val), 0) devicetimeoffset = now.sub(devicetime) ctxa = context.withvalue(ctxa, devicetimediffkey{}, devicetimeoffset) } res, err := next(ctxa, req) // set to response header if value is set if devicetimeoffset != 0 { res.header().set("device-time-offset", fmt.sprintf("%d", devicetimeoffset)) } return res, err } } }
然后你就会得到回应:
curl -v \ --header "Content-Type: application/json" --header "now-on-device: 1656442814" \ --data '{"name": "Jane"}' \ http://localhost:8080/greet.v1.GreetService/Greet * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /greet.v1.GreetService/Greet HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.79.1 > Accept: */* > Content-Type: application/json > now-on-device: 1656442814 > Content-Length: 16 > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Accept-Encoding: gzip < Content-Type: application/json < Device-Time-Offset: 7259524766000 < Greet-Version: v1 < Date: Tue, 28 Jun 2022 21:01:13 GMT < Content-Length: 27 < * Connection #0 to host localhost left intact {"greeting":"Hello, Jane!"}
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《更改connect-go拦截器中的响应体》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习