'Retrieve response metadata in interceptor in gRPC-go
For short:
How can I retrieve response's initial_metadata with grpc.WithStreamInterceptor
without wrapping grpc.ClientStream
?
The situation is I try to use an interceptor to manage metadata in request and response from all calls. For example, append password/token to request and retrieve token from response.
First I create a struct and define a streamInterceptor
type ClientInterceptor struct {
Header string
Value string
ResponseHeader string
ResponseValue string
}
func (client *ClientInterceptor) streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
log.Println("Add header in interceptor")
ctx = metadata.AppendToOutgoingContext(ctx, client.Header, client.Value)
clientStream, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
return nil, err
}
log.Println("Access response header")
responseMeta, err := s.Header() // Block at here, server not trigger any function
if err != nil {
return nil, err
}
if val, ok := responseMeta[client.ResponseHeader]; ok {
client.ResponseValue = val[0]
log.Println("Response header: ", val[0])
} else {
return nil, status.Errorf(codes.DataLoss, "Response without header founded")
}
return clientStream, nil
}
and below is how I use it.
interceptor := &ClientInterceptor{Header: "x-custom", Value: "hello header", ResponseHeader: "x-custom-echo"}
conn, err := grpc.Dial("127.0.0.1:60661", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStreamInterceptor(interceptor.streamInterceptor))
However, the code is always block on the line responseMeta, err := s.Header()
, and the server shows no request.
If I remove the part of the code and use a struct to wrap clientStream
, I can get response metadata from RecvMsg()
type wrappedStream struct {
grpc.ClientStream
getHeader bool
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
if !w.getHeader {
responseMeta, err := w.Header()
if err != nil {
return nil
}
if val, ok := responseMeta["x-custom-echo"]; ok {
log.Println("Response header: ", val[0])
}
}
return w.ClientStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
return w.ClientStream.SendMsg(m)
}
This means that every call will create it's own instance and store the token by their own, which is not what I want.
I know I can use a pointer to share the values, but it seems a bit "dirty".
According to the gRPC official document, initial_metadata can be send first without body(protobuf).
How can I forced metadata to be send in interceptor, since server will response metadata first when request header comes (request body is unnecessary).
To reproduce the situation, I write it in gist if you need.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|