Reputation:
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
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
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