我正在使用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方法实现了这一点,所以我想看看是否可以使用interceptor来处理它,这样我就不需要确保它在所有单个RPC方法实现中都得到了处理.
由于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.然后我想我可以使用一个类型switch ,就像这样(上面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方法中单独实现更简单.
是否有更简单的方法可以使用拦截器修改响应中的字段?还是有其他方法可以让我这么做?