Cast request in gRPC interceptor to relevant protobuf message - go

I have a UnaryServerInterceptor that receives a req Interface{}. This req could be any one of my messages, but in this case all my messages have a metadata child message in common.
Protobuf definitions (sample)
message ClientMeta {
string info = 1;
}
message PingRequest {
ClientMeta metadata = 1;
}
message OtherRequest {
ClientMeta metadata = 1;
}
service Blah {
rpc Ping (PingRequest) returns (PingResponse) {}
rpc Other (OtherRequest) returns (OtherResponses) {}
}
Interceptor
func (i *authInterceptor) unary() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
log.Info().Msgf("interceptor: %s", info.FullMethod)
}
}
I need to access the properties of the message's metadata in my interceptor. The problem is that I don't know which message it is, so I can't just cast it. I have several different messages but they all have metadata in common.
What's the best way to go about this?

The protoc generation should have produced a method called GetMetadata for both types. You can check if the incoming message implements an interface using a type assertion (see the tour of go for more details), then call that method to get the metadata.
type HasMetadata interface {
GetMetadata() *ClientMeta
}
In the function that handles the message, do something like this
if m, ok := req.(HasMetadata); ok {
return m.GetMetadata()
}

Related

gRPC stream interceptor not passing context to request method

I have the following gRPC interceptor running serverside, which wraps a serverstream and passes it on to the next handler:
// HarmonyContext contains a custom context for passing data from middleware to handlers
type HarmonyContext struct {
context.Context
Request interface{}
UserID uint64
Limiter *rate.Limiter
}
type IHarmonyWrappedServerStream interface {
GetWrappedContext() HarmonyContext
}
type HarmonyWrappedServerStream struct {
grpc.ServerStream
WrappedContext HarmonyContext
}
func (ss HarmonyWrappedServerStream) GetWrappedContext() HarmonyContext {
return ss.WrappedContext
}
func (m Middlewares) HarmonyContextInterceptorStream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
wrapped := WrapServerStream(ss)
return handler(srv, wrapped)
}
func WrapServerStream(stream grpc.ServerStream) HarmonyWrappedServerStream {
if existing, ok := stream.(HarmonyWrappedServerStream); ok {
return existing
}
return HarmonyWrappedServerStream{ServerStream: stream, WrappedContext: HarmonyContext{
Context: stream.Context(),
}}
}
and in the handler itself, I have the following code:
func (v1 *V1) StreamGuildEvents(r *corev1.StreamGuildEventsRequest, s corev1.CoreService_StreamGuildEventsServer) error {
wrappedStream := s.(middleware.IHarmonyWrappedServerStream)
println(wrappedStream)
return nil
}
However, I get the following runtime error when sending a streaming request:
interface conversion: *corev1.coreServiceStreamGuildEventsServer is not middleware.IHarmonyWrappedServerStream: missing method GetWrappedContext
In fact, the ServerStream in the handler is completely different from the one in the interceptors. Is there any way to make the interceptor pass the custom ServerStream properly?
We've opened up one of our repos to demonstrate this if it helps anybody.
https://github.com/drud/api-common/blob/main/interceptors/state.go#L207
Very similar to #Bluskript impl however I do not have the same issue encountered above. I believe this might be from the return type grpc.StreamServerInterceptor and where I am using the context getter.

How to deal with go RPC calling with an array as its return value?

I want to export my service as an RPC service and my local method is to get all users(struct type) from db such as follows,
func GetUsers() ([]model.User) {
// business logic
}
Now I wrapped the method as the RPC patterns as follows,
func (api *API) RpcGetUsers(_, reply []*model.User) error {
reply = dal.GetUsers()
return nil
}
But When I ran the function an panic occurred. It mentioned that
"reply type of method "RpcGetUsers" is not a pointer: "[]*model.User""
How can I solve this issue?
RPC documentation says the method must look like:
func (t *T) MethodName(argType T1, replyType *T2) error
So you need a request and a reply type. You can do something like this:
type Empty struct{}
type Users struct {
Users []model.User
}
func (api *API) RpcGetUsers(_ *Empty, reply *Users) error {
...
}

unmarshall request in gRPC interceptor

In order to perform Authorization, some attributes from the request is to be read so that input for Authorization Server can be made
For example, this is the interceptor. Here prepareAuthZInput is called to preparing the input
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
input := prepareAuthZInput(info.FullMethod, req)
}
In this function, there's a big if-else part which checks for the actual type for the request, type casts it and then performs the input preparation.
func prepareAuthZInput(method string, req interface{}) {
var input Input
if methodName = "/Data/Call" {
callRequest, ok := req.(CallRequest)
if ok {
// prepare input from callRequest
}
} else if methodName = "/Data/Receive" {
receiveRequest, ok := req.(ReceiveRequest)
if ok {
// prepare input from receiveRequest
}
}
return input
}
How can I improve this code?
When doing something like this, it's typical to add auth data to the metadata instead of the request messages. This way the server doesn't need to inspect all the possible request payload types.
If you must use the request payload, it would be more idiomatic to use a type switch instead:
switch r := req.(type) {
case CallRequest: // r is a CallRequest...
case ReceiveRequest: // r is a ReceiveRequest...
default:
return status.Errorf(codes.Unimplemented, "unknown request type: %T", req)
}

Accessing gRPC request object in custom interceptor/middleware

gRPC's Go library provides interfaces for creating your own custom interceptors (i.e. middleware functions), and I'm attempting to write two logging interceptors. The first is a Unary Server Interceptor where I'm easily able to log the request parameters using the object passed into the interceptor function.
func loggingUnary(context context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(context, req)
printLogMessage(err, info.FullMethod, context, time.Since(start), req)
return resp, err
}
How can I do the same with the Stream Server Interceptor which doesn't conveniently pass the request object as a parameter? Is there another way to access the request?
func loggingStream(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
start := time.Now()
err := handler(srv, stream)
printLogMessage(err, info.FullMethod, stream.Context(), time.Since(start), "")
return err
}
This is a bit old now, but the easiest way to extend your interception into the stream is to create a grpc.ServerStream wrapper, then wrap the real ServerStream in your interceptor. In that way, your intercepting code can handle the received and sent messages in the stream.
// A wrapper for the real grpc.ServerStream
type LoggingServerStream struct {
inner grpc.ServerStream
}
func (l LoggingServerStream) SetHeader(m metadata.MD) error {
return l.SetHeader(m)
}
func (l LoggingServerStream) SendHeader(m metadata.MD) error {
return l.SendHeader(m)
}
func (l LoggingServerStream) SetTrailer(m metadata.MD) {
l.SetTrailer(m)
}
func (l LoggingServerStream) Context() context.Context {
return l.Context()
}
func (l LoggingServerStream) SendMsg(m interface{}) error {
fmt.Printf("Sending Message: type=%s\n", reflect.TypeOf(m).String())
return l.SendMsg(m)
}
func (l LoggingServerStream) RecvMsg(m interface{}) error {
fmt.Printf("Receiving Message: type=%s\n", reflect.TypeOf(m).String())
return l.RecvMsg(m)
}
The interceptor:
func LoggingStreamInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return handler(srv, LoggingServerStream{inner:ss})
}
}
Any state you need to keep and go in your wrapper.
The stream handler can be called multiple times during the lifetime of the request that created the stream, that's why the request is not part of the arguments to the handler (and to any interceptor, either). You could place the request (or better, a copy of the data that you want logged and not a reference to the request itself) in the stream context (assuming you are in control of the code that creates the ServerStream object). I would rather log the request parameters once, when the stream is created and not on every call to the handler (so each request is logged once only).

Calling a registered RPC method on same server in Golang

I have some methods registered to a type in server program for RPC in Golang. I want to use those methods from clients as well as the server itself. e.g. I have an addition method registered as RPC method which I want to invoke from clients. But I want to call the same method in the same server program for some other function too. Is there a way to do this?
Difficult to answer, if we don't know how you build your code with some exemple it will be easier.
Two choices.
Create client with goroutine, set your connection to the server and call the method. This method is bad, wtf.
Second :
type CalcServ struct {
LastAnswer
}
type CalcReq struct {
X int
Y int
}
type CalcResp struct {
Z int
}
func (serv *CalcServ) Add(req *CalcReq, resp *CalcResp) error {
serv.LastAnswer = req.X + req.Y
serv.Show(req, resp)
return nil
}
func (serv *CalcServ) Show(req *CalcReq, resp *CalcResp) error {
resp.Z = serv.LastAnswer
return nil
}
My exemple is not a good implementation, but it's just to show the idea to you.
There are Two RPC method, Add and Show, and Add call Show
I had to do this myself. This is a contrived example showing what I did in my case:
This is the RPC function...
func (server *Server) RemoteGoroutineCount(request GoroutineCountsRequest, response *GoroutineCountsResponse) error {
response.Count = runtime.NumGoroutine()
return nil
}
Now, if I want to call that locally, I can just do
req := GoroutineCountsRequest{}
res := &GorouteCountsResponse{}
s := &Server{}
err := s.RemoteGoroutineCount(req, res)
// handle error
// Now res has your filled in response
res.NumGoroutines

Resources