Home > Blockchain >  How can I make type assertions against an anonymous function?
How can I make type assertions against an anonymous function?

Time:10-18

I'm writing an HTTP service in Go using Gorilla. I'm newish to Go (<1yr experience), but ramping up fairly quickly.

I have a function I use to register my handlers:

func (s *Server) RegisterHandler(path string, handler http.HandlerFunc, methods ...string) {
    if len(methods) == 0 {
        s.Router.Handle(path, handler).Methods(http.MethodGet)
    } else {
        s.Router.Handle(path, handler).Methods(methods...)
    }
}

I have some code that registers named functions as handlers:

func (s *Server) RegisterDefaultHandlers() {
    s.RegisterHandler("/ping", Ping)
}

func Ping(w http.ResponseWriter, request *http.Request) {
    responders.RespondOk(w)
}

I also have unit test code that registers anonymous functions as handlers:

s.RegisterHandler("/testPath", func(w http.ResponseWriter, r *http.Request) {
    // whatever the test calls for
}, http.MethodPost)

This all works great -- just establishing my starting point.

Today, I find myself banging my head against Go's type system. I am defining some custom handler types, for example:

type UserAwareHandlerFunc func(http.ResponseWriter, *http.Request, models.User)

And I'm also introducing a function to allow me to register such handlers, and to wrap all handlers in context.ClearHandler. If this works, I'll also wrap everything with another function that sets a few things on my logging context. What I have so far:

func (s *Server) RegisterHandler(path string, handler interface{}, methods ...string) {
    wrappedHandler := wrappers.ApplyWrappers(handler)
    if len(methods) == 0 {
        s.Router.Handle(path, wrappedHandler).Methods(http.MethodGet)
    } else {
        s.Router.Handle(path, wrappedHandler).Methods(methods...)
    }
}

func ApplyWrappers(handler interface{}) http.Handler {
    var result http.Handler
    if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
        result = UserAware(userAwareHandler)
    } else if handlerFunc, ok := handler.(http.HandlerFunc); ok {
        result = handlerFunc
    } else if handlerObj, ok := handler.(http.Handler); ok {
        result = handlerObj
    } else {
        log.Fatalf("handler % v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
    }

    // to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
    // for all handlers -- see https://stackoverflow.com/a/48203334
    result = context.ClearHandler(result)
    return result
}

func UserAware(handler UserAwareHandlerFunc) http.Handler {
    return func(w http.ResponseWriter, r *http.Request) {
        user := ... // get user from session
        handler(w, r, user)    
    }
}

With these changes, I can no longer register named or anonymous functions; the type assertions in ApplyWrappers all fail. I have to declare and define a typed variable, then pass that in.

Named functions have two feasible approaches:

var Ping http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) {
    responders.RespondOk(w)
}

func Ping2(w http.ResponseWriter, request *http.Request) {
    responders.RespondOk(w)
}

func (s *Server) RegisterDefaultHandlers() {
    s.RegisterHandler("/ping", Ping)

    var pingHandler2 http.HandlerFunc = Ping2
    s.RegisterHandler("/ping2", pingHandler2)
}

For anonymous functions, I can do:

var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
    ...
}
s.RegisterHandler("/testPath", handler, http.MethodPost)

The whole point of what I've built here is to consolidate boilerplate into one place, keeping my many tests and handlers as streamlined as possible. The need to declare a typed variable is working against that goal. So my question is this: is there some special type magic I could use (preferably in RegisterHandler and/or ApplyWrappers) that would restore the ability to pass named and/or anonymous functions to RegisterHandler?

EDIT: thanks so much for the quick answers. Problem solved:

func ApplyWrappers(handler interface{}) http.Handler {
    var result http.Handler
    if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
        result = UserAware(userAwareHandler)
    } else if anonymousFunc, ok := handler.(func(http.ResponseWriter,*http.Request)); ok {
        result = http.HandlerFunc(anonymousFunc)
    } else if handlerObj, ok := handler.(http.Handler); ok {
        result = handlerObj
    } else {
        log.Fatalf("handler % v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
    }

    // to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
    // for all handlers -- see https://stackoverflow.com/a/48203334
    result = context.ClearHandler(result)
    return result
}

It's working now, but I still have questions:

  1. If I understand correctly, the "duck typing" behavior that I was looking for here would have been fine if I were dealing with interfaces rather than functions. What drives the distinction? Is duck-typing of functions something I could reasonably hope to see in a future version of the language?
  2. I can cast the anonymous function to a HandlerFunc. It's weird to me that casting and type assertions don't share semantics. Can someone explain?

CodePudding user response:

http.HandlerFunc is a specific type. The Ping function is NOT of that type, although it can be converted to that type since they both have the same underlying type.

If handler is an anonymous function then you need to use an anonymous function in the type assertion:

f, ok := handler.(func(http.ResponseWriter,*http.Request))

https://golang.org/ref/spec#Type_assertions

  • if T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T
  • If T is an interface type, x.(T) asserts that the dynamic type of x implements the interface T.
  • Related