user5047085
user5047085

Reputation:

panic: Last argument needs to be of type http.HandlerFunc

I have this helper function, which is compiling fine:

func Middleware(adapters ...interface{}) http.HandlerFunc {

    log.Info("length of adapters:", len(adapters))

    if len(adapters) < 1 {
        panic("Adapters need to have length > 0.");
    }

    h, ok := (adapters[len(adapters)-1]).(http.HandlerFunc)

    if ok == false {
        panic("Last argument needs to be of type http.HandlerFunc") // ERROR HERE
    }

    adapters = adapters[:len(adapters)-1]

    for _, adapt := range adapters {
        h = (adapt.(AdapterFunc))(h)
    }

    return h

}

I am calling it like so:

router.HandleFunc("/share", h.makeGetMany(v)).Methods("GET")

func (h Handler) makeGetMany(v Injection) http.HandlerFunc {
    return mw.Middleware(
        mw.Allow("admin"),
        func(w http.ResponseWriter, r *http.Request) {
            log.Println("now we are sending response.");
            json.NewEncoder(w).Encode(v.Share)
        },
    )
}

the problem is that I am getting this error and I cannot figure out why:

    panic: Last argument needs to be of type http.HandlerFunc

    goroutine 1 [running]:
    huru/mw.Middleware(0xc420083d40, 0x2, 0x2, 0xc42011f3c0)
            /home/oleg/codes/huru/api/src/huru/mw/middleware.go:301 +0x187
    huru/routes/share.Handler.makeGetMany(0xc4200ae1e0, 0x10)
            /home/oleg/codes/huru/api/src/huru/routes/share/share.go:62 +0x108

it does confirm that the length of the adapters slice is 2:

 length of adapters:2

anyone know why that type assertion would fail in this case? Makes no sense. Maybe I am not actually retrieving the last argument of the slice or something? Is there a better way to pop the last argument off the slice?

Upvotes: 1

Views: 302

Answers (2)

nos
nos

Reputation: 20880

In the http package, ListenAndServe has the following signiture

func ListenAndServe(addr string, handler Handler) error

where the Handler (i.e., http.Handler) is an interface

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

For one web application, there is only one ListenAndServe call, thus only onehandler, which needs to deal with all the access points such as /url1, /url2, etc. If we write the handler from scratch, the implementation could be a custom struct that wraps around a database handle. Its ServeHTTP method checks the access point, and writes the corresponding content to the ResponseWriter, which is quite tedious and mingled.

That's the motivation of ServeMux, which is the router in your code. It has a ServeHTTP method so it satisfies the http.Handler interface and can be used for ListenAndServe. In addition, it has the HandleFunc method to deal with the individual access point

func (mux *ServeMux) HandleFunc(pattern string,
                                handler func(ResponseWriter, *Request))

Note here the handler is not a http.Handler, i.e., it doesn't have ServeHTTP method. It doesn't have to because the mux already has ServeHTTP and its ServeHTTP method can dispatch the individual access point request to the corresponding handlers.

Note it also has a Handle method, which requires the argument to satisfy the http.Handler interface. It's slightly less convenient to use compared to the HandleFunc method.

func (mux *ServeMux) Handle(pattern string, handler Handler)

Now back to your question, since you call router.HandleFunc, its input doesn't have to be http.Handler. So an alternative solution is to use func(ResponseWriter, *Request) as the return type for your middleware and makeGetMany method. (the type assertion in the middleware also needs to be updated, probably a lot more code needs to be updated as well)

@xpare's solution is to do type conversion so that all the function signatures match up, i.e., convert func(ResponseWriter, *Request) to http.HandlerFunc. It is also interesting to see how it works. From the implementation

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

you can see that it basically defines a ServeHTTP method to call itself.

Upvotes: 0

novalagung
novalagung

Reputation: 11532

You need to wrap the 2nd argument of mw.Middleware() statement into http.Handler type by using http.HandlerFunc().

func (h Handler) makeGetMany(v Injection) http.HandlerFunc {
    return mw.Middleware(
        mw.Allow("admin"),
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Println("now we are sending response.");
            json.NewEncoder(w).Encode(v.Share)
        }),
    )
}

Upvotes: 2

Related Questions