unmarshall request in gRPC interceptor - go

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

Related

Access information about the request and response payloads in grpc-go's stat/HandleRPC

I am using stats/HandleRPC() to emit some metrics about the RPC duration, when I receive the stats/End data, and I want to tag the metrics with some information that can be extracted from the incoming and outgoing payloads. What would be the best way to achieve this?
func (h *myStatsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) {
switch stat := rpcStats.(type) {
case *stats.End:
durationMs := stat.EndTime.Sub(stat.BeginTime).Seconds() * 1000.0
// Now before sending this value, I need to know, for example the value of a specific key in the request payload, or whether the response is nil or not
}
}
In your implementation of TagRPC, you can create a struct and add a pointer to it to the context. Then add information in it over the successive calls to HandleRPC. So if you need something from the Payload that's only available in the *stats.InPayload invocation, you can pull it out and store it in the struct you added to the context, and then access it later when HandleRPC is called again with *stats.End
type recorderCtxKey struct{}
type recorder struct {
size int64
}
func (sl *statsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
return context.WithValue(ctx, rpcStatCtxKey{}, &recorder{})
}
func (h *statsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) {
switch stat := rpcStats.(type) {
case *stats.InPayload:
r, _ := ctx.Value(recorderContextKey{}).(*Recorder)
r.size += stat.WireLength
case *stats.End:
durationMs := stat.EndTime.Sub(stat.BeginTime).Seconds() * 1000.0
r, _ := ctx.Value(recorderContextKey{}).(*Recorder)
# use r.size #
}
}

Pass argument down to next handle func from middleware

I would like to make a reusable middleware for validation throughout my API. Here, validation is done through govalidator, so I just need to pass the validation rules and a DTO where the request is mapped into.
func ValidationMiddleware(next http.HandlerFunc, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
utils.SetResponseHeaders(rw)
opts := govalidator.Options{
Request: r,
Data: &dto,
Rules: validationRules,
RequiredDefault: true,
}
v := govalidator.New(opts)
err := v.ValidateJSON()
if err != nil {
fmt.Println("Middleware found an error")
err := utils.ErrorWrapper{
StatusCode: 400,
Type: "Bad Request",
Message: err,
}
err.ThrowError(rw)
return
}
next(rw, r)
}
}
This is how the HandleFunc looks like:
var rules govalidator.MapData = govalidator.MapData{
"name": []string{"required"},
"description": []string{"required"},
"price": []string{"required", "float"},
}
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
If no errors are found, govalidator parses the request body into the dto struct, so I would like to pass this down into the next handler and avoid trying to parse the body a second time.
How can I pass this struct down to the next HandleFunc?
From the code, it appears like you pass the request to the validator options, and the validator reads and validates the body from that. This poses several problems: An HTTP request can only be read once, so either the validator somehow returns you that unmarshaled object, or you have to read the body before validating it.
Going for the second solution, the first thing is your validator has to know the type of the object it has to unmarshal to:
func ValidationMiddleware(next http.HandlerFunc, factory func() interface{}, validationRules govalidator.MapData, dto interface{}) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
newInstance:=factory()
data, err:=ioutil.ReadAll(r.Body)
json.Unmarshal(data,newInstance)
// Validate newInstance here
r.WithContext(context.WithValue(r.Context(),"data",newInstance))
next(wr,r)
}
}
Where the func factory is a function that creates an instance of the object that will be unmarshaled for a request:
func RegisterItemsRouter(router *mux.Router) {
itemsRouter := router.PathPrefix("/inventory").Subrouter()
itemsRouter.HandleFunc("/", middleware.ValidationMiddleware(addItem, func() interface{} { return &TheStruct{}}, rules, dto.CreateItem{
Name: "",
Description: "",
Price: govalidator.Float64{},
})).Methods("POST")
}
This way, when a new request comes, a new instance of TheStruct will be created and unmarshaled, then validated. If the validation is ok, it will be placed into the context, so the next middleware or the handler can get it:
func handler(wr http.ResponseWriter,r *http.Request) {
item:=r.Context().Value("data").(*TheStruct)
...
}

Bind Query inside middleware

I'm trying to write a "Binder" middleware that will validate any request query using a struct type with gin bindings/validators
So for example, let's say I have an endpoint group called /api/subject which requires the query string to have a subject code and an ID that will be validated using the following struct (called entity.Subject):
type Subject struct {
Code string `binding:"required,alphanum"`
ID string `binding:"required,alphanum,len=4"`
}
That's just one example, but I'd like to be able to pass any struct type to this middleware, because I'd like to access the query data on future handlers without worrying about query validation.
So I tried something like this:
func Binder(t reflect.Type) gin.HandlerFunc {
return func(c *gin.Context) {
obj := reflect.New(t).Elem().Interface()
if err := c.BindQuery(&obj); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
c.Set(t.Name(), obj)
}
}
And added this middleware like so:
apiGroup := router.Group("/api")
{
// other subgroups/endpoints
// ...
subjectGroup := apiGroup.Group("/subject", middleware.Binder(reflect.TypeOf(entity.Subject{})))
}
And later on, in another handler function, let's say GetSubject, I want to access the subject data passed by doing c.MustGet("Subject").(entity.Subject)
But this isn't working =(, when I print obj, it's just an empty interface, how would I do this?
I managed to do something similar!
I created the following middleware
var allowedTypes = []binding.Binding{
binding.Query,
binding.Form,
binding.FormPost,
binding.FormMultipart,
}
func Bind(name string, data interface{}, bindingType binding.Binding) gin.HandlerFunc {
return func(ctx *gin.Context) {
ok := false
for _, b := range allowedTypes {
if b == bindingType {
ok = true
}
}
if !ok {
ctx.AbortWithError(
http.StatusInternalServerError,
fmt.Errorf("Bind function only allows %v\n", allowedTypes),
)
}
_ = ctx.MustBindWith(data, bindingType)
ctx.Set(name, data)
}
}
Remember to pass a pointer to your desired type in the call, like so:
router.GET("/something", Bind("Object", &myObject, binding.Query))
I restricted only to a few binding types because they allow ShouldBind to be called multiple times, whereas JSON, XML and others consume the Request body.
This way you can pass multiple Bind middlewares and if the validation fails it automatically aborts with http.StatusBadRequest

How to access request in stream interceptor?

I have a unary interceptor that contains the following code:
func (m Middlewares) LocationInterceptor(c context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ctx := c.(HarmonyContext)
location, ok := req.(interface{ GetLocation() *corev1.Location })
if !ok {
panic("location middleware used on message without a location")
}
ctx.Location := location.GetLocation()
return handler(c, req)
}
How would I be able to convert this to a stream interceptor, if I know that the stream will definitely only stream from server to client? In addition, is there any way to make it only intercept when the moment the stream begins?
func (m Middlewares) LocationInterceptorStream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
wrappedStream := ss.(HarmonyWrappedServerStream)
return handler(srv, wrappedStream)
}
For a "server streaming RPC", the client sends one message (the request), and the server responds with multiple messages. In the stream interceptor, you need to call ServerStream.RecvMsg(...) once to get the request from the client. You will then need to pass a "wrapped stream" into handler which will later return this message on the first call to RecvMsg. For gRPC using protobuf messages, you can do something like the following. This serverStreamWrapper will allow you to call peekRequest(...) in the interceptor to get the request, then pass the stream on to the handler.
For this specific example, you may also be able to implement the logic in the wrapped RecvMsg(...) function itself. This function gets called by gRPC to read the request from the client to the server. This may be simpler than trying to store the request.
type serverStreamWrapper struct {
peekedRequest proto.Message
wrappedStream grpc.ServerStream
}
func newServerStreamWrapper(stream grpc.ServerStream) *serverStreamWrapper {
return &serverStreamWrapper{nil, stream}
}
func (s *serverStreamWrapper) peekRequest(msg interface{}) error {
protoMsg := msg.(proto.Message)
if protoMsg == nil {
panic("BUG: msg must not be nil")
}
if s.peekedRequest != nil {
panic("BUG: Must only called peekRequest once")
}
err := s.wrappedStream.RecvMsg(protoMsg)
if err == nil {
s.peekedRequest = protoMsg
}
return err
}
func (s *serverStreamWrapper) RecvMsg(msg interface{}) error {
if s.peekedRequest != nil {
protoMsg := msg.(proto.Message)
proto.Reset(protoMsg)
proto.Merge(protoMsg, s.peekedRequest)
s.peekedRequest = nil
return nil
}
return s.wrappedStream.RecvMsg(msg)
}

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

Resources