Go中GRPC Unary拦截器Metadata无法更新的问题及修复
gRPC Unary拦截器添加Request ID失败问题修复
问题描述
尝试编写gRPC Unary拦截器为入站请求添加请求ID,原代码如下:
func RequestIDInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (val interface{}, err error) { newCtx := ctx newCtx = createAndSetMetadata(newCtx, metadata.FromIncomingContext, metadata.NewIncomingContext) newCtx = createAndSetMetadata(newCtx, metadata.FromOutgoingContext, metadata.NewOutgoingContext) md, ok := metadata.FromOutgoingContext(newCtx) fmt.Printf("After update. Metadata: %v, Found: %t\n", md, ok) return handler(newCtx, req) } func createAndSetMetadata(ctx context.Context, getter func(context.Context) (metadata.MD, bool), setter func(context.Context, metadata.MD) context.Context) context.Context { meta, ok := getter(ctx) fmt.Printf("Found? %t, Metadata: %v\n", ok, meta) if !ok { meta = metadata.New(map[string]string{}) ctx = setter(ctx, meta) } meta.Set("X-Request-Id", "TEST_REQUEST_ID") fmt.Printf("Metadata: %v\n", meta) return ctx }
现象:
- 当请求不包含Metadata时(调用
RequestIDInterceptor(context.Background(), nil, nil, handler)),代码运行正常; - 当上下文已包含Metadata时,请求ID无法被填充。
输出对比:
WITHOUT METADATA Found? false, Metadata: map[] Metadata: map[x-request-id:[TEST_REQUEST_ID]] Found? false, Metadata: map[] Metadata: map[x-request-id:[TEST_REQUEST_ID]] After update. Metadata: map[x-request-id:[TEST_REQUEST_ID]], Found: true WITH METADATA Found? true, Metadata: map[] Metadata: map[x-request-id:[TEST_REQUEST_ID]] Found? true, Metadata: map[] Metadata: map[x-request-id:[TEST_REQUEST_ID]] After update. Metadata: map[], Found: true
猜测原因:metadata.MD按值传递,导致createAndSetMetadata返回后更新被丢弃。
问题分析
你的猜测完全正确:metadata.MD本质是map[string][]string的别名,虽然map本身是引用类型,但getter(比如metadata.FromIncomingContext)返回的是MD的副本。修改这个副本的内容不会影响上下文中原有的MD,必须将修改后的MD重新通过setter存入上下文,才能让后续操作看到更新。
修复代码
修改createAndSetMetadata函数,无论是否存在原有Metadata,在修改MD后都重新调用setter更新上下文:
func RequestIDInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (val interface{}, err error) { newCtx := ctx newCtx = createAndSetMetadata(newCtx, metadata.FromIncomingContext, metadata.NewIncomingContext) newCtx = createAndSetMetadata(newCtx, metadata.FromOutgoingContext, metadata.NewOutgoingContext) md, ok := metadata.FromOutgoingContext(newCtx) fmt.Printf("After update. Metadata: %v, Found: %t\n", md, ok) return handler(newCtx, req) } func createAndSetMetadata(ctx context.Context, getter func(context.Context) (metadata.MD, bool), setter func(context.Context, metadata.MD) context.Context) context.Context { meta, ok := getter(ctx) fmt.Printf("Found? %t, Metadata: %v\n", ok, meta) if !ok { meta = metadata.New(map[string]string{}) } // 修改MD内容 meta.Set("X-Request-Id", "TEST_REQUEST_ID") fmt.Printf("Metadata: %v\n", meta) // 无论是否新建,都重新将MD存入上下文 return setter(ctx, meta) }
修复说明
- 移除了原代码中仅在无Metadata时调用
setter的逻辑; - 无论原有Metadata是否存在,修改MD内容后都调用
setter将更新后的MD重新绑定到上下文; - 这样后续从上下文中获取的MD就能看到添加的
X-Request-Id了。
内容的提问来源于stack exchange,提问作者Woody1193
相关产品推荐
相关产品推荐

