gRPC stream interceptor not passing context to request method - go

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.

Related

Cast request in gRPC interceptor to relevant protobuf message

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()
}

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 {
...
}

Mocking an external Library for unit test

I have a below function tryGet() to unit test:
type config struct {
Key string `json:"key"`
Client todo.Client `json:"client"`
}
var instance *config
func (c *config) tryGet() error {
client := &http.Client{}
tClient := Client{"http://url", client}
configValues := config{"Key", tClient}
Instance := &configValues
err := Instance.Client.perform("GET", header)
return nil
}
// External library in package named "todo" has the below structs and functions
package todo
type Client struct {
BaseURL string
HTTPClient *http.Client
}
func (client *Client) perform() error {
return nil
}
I am finding a hard time to mock the Client and perforn in external package todo
If the external library is not under your control, which I assume is the case, then you should assume that the code within is tested, therefore you should create the boundary at a point that you have control of the code.
To do this you should create the interface at the config struct boundary.
type ClientInterface interface {
perform() error
}
type config struct {
Url string `json:"url"`
Client ClientInterface `json:"client"`
}
var instance *config
func (c *config) tryGet() error {
err := c.Client.perform("GET", header)
return nil
}
By doing it this way, you don't care about testing the lower level code base and you just care that this module has a perform function and that given certain conditions your code behaves correctly.
You can then create a mock todo.Cient struct that you can replace the normal one with and have it return all sorts of things and behaviors to test your code.
You can mock the function as follow
type myImpl todo.Client
func (client *myImpl) perform() error {
// do what you want to assert in the test
return nil
}
And then you will use myImpl whenever you have to use todo.Client
if you are using a function with a parameter of type todo.Client, it will not work if you pass an argument of type myImpl. It will throw an error:
cannot use client (type myImpl) as type todo.Client in field value
To solve this issue, an interface can be created
type Client interface {
perform() error
}
Now the type Client should replace the type todo.Client of the function to be unit tested.
type config struct {
Url string `json:"url"`
Client Client `json:"client"`
}
With this change the above code which supply an implementation myImpl of the interface Client should work

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).

Json decoding into struct, using different request type depending on path

I want to decode json to struct. My structs look like that:
type message struct {
Request baseRequest `json:"request"` //actually there should be other type here, but I can't think of what it could be
Auth auth `json:"auth"`
}
type baseRequest struct {
Foo string `json:"foo" validate:"required"`
}
type createRequest struct {
baseRequest
Bar string `json:"bar" validate:"required"`
}
type cancelRequest struct{
baseRequest
FooBar string `json:"foo_bar" validate:"required"`
}
I want to compose createRequest with baseRequest. All my code is revolving around passing message type in chain of responsibility pattern. I have implemented a handler, that creates a pointer to empty message struct, that is used by jsonReader() function. For /create path I want to use createRequest instead of baseRequest, and for /cancel path I want to use cancelRequest. So for example in:
func (factory *HandlerFactory) Create() http.Handler {
create := func() *message { return &message{} }
return factory.defaultChain(createNewMessage, nil)
}
I want to change the type of message.Request() to createRequest.
And in:
func (factory *HandlerFactory) Cancel() http.Handler {
create := func() *message { return &message{} }
return factory.defaultChain(createNewMessage, nil)
}
I want to change the type of message.Request to cancelRequest. How can I achieve something like that in Go?
Unfortunately what you are trying to do is not really how Go works, since Go does not have polymorphism - createRequest and cancelRequest do not inherit from baseRequest, they contain a baseRequest. So when you have baseRequest in your message struct, it can't be anything other than a baseRequest.
You may need to use separate types for each message. One for the cancel request, and a different one for the create request.
Here is an article discussing inheritance and composition in Go, and contrasts it with Java. It should help you grok how struct embedding works in Go.
Actually, I found the way. You could implement message like this:
type message struct {
Request request `json:"request"` //actually there should be other type here, but I can't think of what it could be
Auth auth `json:"auth"`
}
Where request is an interface, which has serialize()
type request interface {
serialize() ([]byte, error)
}
Then you can implement serialize() for every request type, and initialize message like this:
func (factory *HandlerFactory) Cancel() http.Handler {
create := func() *message { return &message{&cancelRequest{}, auth{}} }
return factory.defaultChain(createNewMessage, nil)
}

Resources