8

How is the following function implemented?

func handle(pattern string, handler interface{}) {
    // ... what goes here? ...
    http.Handle(pattern, ?)
}

handle("/foo", func(w http.ResponseWriter, r http.Request) { io.WriteString(w, "foo") }
handle("/bar", BarHandler{})

handle() is passed either a function which matches the type of http.HandlerFunc or a type which implements the http.Handler interface.

2
  • Would it really be that bad to just change the second argument to an http.Handler and force the caller to convert functions to http.HandlerFuncs? Commented Jun 16, 2011 at 20:50
  • It's not the end of the world, but it's a bit of a pain to HandleFoo foo-type funcs and HandleBar bar-type funcs, and have that spread across the files implementing handlers and the file registering handlers. Commented Jun 16, 2011 at 21:00

2 Answers 2

11

Instead of resorting to reflection, I would do it this way:

func handle(pattern string, handler interface{}) {
    var h http.Handler
    switch handler := handler.(type) {
    case http.Handler:
        h = handler
    case func(http.ResponseWriter, *http.Request):
        h = http.HandlerFunc(handler)
    default:
        // error
    }
    http.Handle(pattern, h)
}
Sign up to request clarification or add additional context in comments.

5 Comments

That works, thanks. btw: any idea why you have to unroll the second case and can't type match against http.HandlerFunc directly?
func(http.ResponseWriter, *http.Request) and http.HandlerFunc are two different types. They can be converted back and forth because they happen to have the same underlying type, but they're still different.
that's why I didn't do it with type-switch. Specifying http.HandlerFunc yourself is just a bad idea, and I didn't find any way to avoid it.
@Elazar, fair enough. Your solution has the advantage that the code doesn't have to change if http.HandlerFunc's signature changes. I just figure lots of stuff's going to be breaking anyway in that case.
my main problem with your solution is not the flexibility but the readability. The user don't understand the context of func(http.ResponseWriter,*http.Request) is not clear. And the type-switch is essentially translated to what I wrote.
2

First we need to introduce the term "reflections" in Java/C#'s terminology, RTTI in C++'s terminology. It's quite simple actually. The compiler keeps data to find out what is the type of an instance var i SomeType during runtime. Go supports reflection, and that's how it finds out what's the type of handler during runtime.

The handle function uses the reflection. A crude example

package main
import ("reflect";"http")
type fakeHandler struct{}
func (frw *fakeHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}

func handle(pattern string, handler interface{}) {
    handlerInterface := reflect.TypeOf(new(http.Handler)).Elem()
    handlerFunction  := reflect.TypeOf(new(http.HandlerFunc)).Elem()
    t := reflect.TypeOf(handler)
    if t.Implements(handlerInterface) {fmt.Println("http.Handler")}
    //http.HandlerFunc is a different type than
    // func(http.ResponseWriter, *http.Request), but we can do
    // var hf HandlerFunc = func(http.ResponseWriter, *http.Request){}
    if t.AssignableTo(handlerFunction) {fmt.Println("http.HandleFunc")}
}
func f(http.ResponseWriter, *http.Request) {}
func main() {
    handle("",&fakeHandler{})
    handle("",f)
}

6 Comments

I think you're on the right track with Implements(), but this results in the compile error: type http.Handler is not an expression. Not sure what it's unhappy with here.
The type switch doesn't actually work (tried it already). In the first case you have to embed the function signature of HandlerFunc otherwise they don't match. In the second case two types which implement ServeHTTP() are not of the same type so again no match, which is where your idea to use Implements() comes in I guess...
@user103576 My answer about the switch type was wrong. Sorry, I deleted it. The first answer is now correct, it compiles runs and is tested.
Great, thanks that works. I am surprised it is so tricky. Reading through the Go docs runtime reflection seemed to be one of the features you are supposed to lean on.
As the other answerer has shown, a type switch does actually work. Sorry for leading you astray. I think in my testing I was accidentally matching against an interface definition instead of a type implementing that interface. Or something... Thanks for the effort.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.