I'm trying to dynamically pass parameters to a URL route handler function. I thought it would be possible to use the reflection package to convert a map of values from the URL to the function that has one parameter that happens to be an anonymous struct. I've gotten as far as creating the struct to pass to the handler function but it ends up being a pointer to the struct. If I change the handler function's signature to expect a pointer the created struct ends up being a pointer to a pointer, I think. At any rate, here's the code (the panic follows):
Link: http://play.golang.org/p/vt_wNY1f08
package main
import (
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"github.com/gorilla/mux"
)
func mapToStruct(obj interface{}, mapping map[string]string) error {
dataStruct := reflect.Indirect(reflect.ValueOf(obj))
if dataStruct.Kind() != reflect.Struct {
return errors.New("expected a pointer to a struct")
}
for key, data := range mapping {
structField := dataStruct.FieldByName(key)
if !structField.CanSet() {
fmt.Println("Can't set")
continue
}
var v interface{}
switch structField.Type().Kind() {
case reflect.Slice:
v = data
case reflect.String:
v = string(data)
case reflect.Bool:
v = string(data) == "1"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
x, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Int64:
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
case reflect.Float32, reflect.Float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return errors.New("arg " + key + " as float64: " + err.Error())
}
v = x
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x, err := strconv.ParseUint(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
v = x
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
structField.Set(reflect.ValueOf(v))
}
return nil
}
type RouteHandler struct {
Handler interface{}
}
func (h RouteHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
t := reflect.TypeOf(h.Handler)
handlerArgs := reflect.New(t.In(0)).Interface()
if err := mapToStruct(handlerArgs, mux.Vars(req)); err != nil {
panic(fmt.Sprintf("Error converting params"))
}
f := reflect.ValueOf(h.Handler)
args := []reflect.Value{reflect.ValueOf(handlerArgs)}
f.Call(args)
fmt.Fprint(w, "Hello World")
}
type App struct {
Router mux.Router
}
func (app *App) Run(bind string, port int) {
bind_to := fmt.Sprintf("%s:%d", bind, port)
http.Handle("/", &app.Router)
http.ListenAndServe(bind_to, &app.Router)
}
func (app *App) Route(pat string, h interface{}) {
app.Router.Handle(pat, RouteHandler{Handler:h})
}
func home(args struct{Category string}) {
fmt.Println("home", args.Category)
}
func main() {
app := &App{}
app.Route("/products/{Category}", home)
app.Run("0.0.0.0", 8080)
}
Panic:
2013/03/28 18:48:43 http: panic serving 127.0.0.1:51204: reflect: Call using *struct { Category string } as type struct { Category string }
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:589 (0x3fb66)
_func_004: buf.Write(debug.Stack())
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:1443 (0x11cdb)
panic: reflect·call(d->fn, d->args, d->siz);
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:428 (0x484ba)
Value.call: panic("reflect: " + method + " using " + xt.String() + " as type " + targ.String())
/usr/local/Cellar/go/1.0.3/src/pkg/reflect/value.go:334 (0x47c3a)
Value.Call: return v.call("Call", in)
/Users/matt/Workspaces/Go/src/pants/pants.go:86 (0x2f36)
RouteHandler.ServeHTTP: f.Call(args)
/Users/matt/Workspaces/Go/src/pants/pants.go:1 (0x347c)
(*RouteHandler).ServeHTTP: package main
/Users/matt/Workspaces/Go/src/github.com/gorilla/mux/mux.go:86 (0x5a699)
com/gorilla/mux.(*Router).ServeHTTP: handler.ServeHTTP(w, req)
/usr/local/Cellar/go/1.0.3/src/pkg/net/http/server.go:669 (0x337b6)
(*conn).serve: handler.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.0.3/src/pkg/runtime/proc.c:271 (0xfde1)
goexit: runtime·goexit(void)
Call Elem() on your reflect.Value object.
Quoting from The Laws of Reflection article:
To get to what p points to, we call the Elem method of Value, which indirects through the pointer
Note that reflect.New()
creates a pointer to a value of the type you pass it. So on the following line:
handlerArgs := reflect.New(t.In(0)).Interface()
handlerArgs
will be a pointer to a struct of type t.In(0)
. You will need to dereference that pointer to have a value suitable to pass to the function.
I'd suggest the following:
Make hanlerArgs
a *reflect.Value
for the actual struct value:
handlerArgs := reflect.New(t.In(0)).Elem()
Make mapToStruct
take such a *reflect.Value
instead of an interface{}
holding a pointer to a struct (it is a helper for your reflection code, after all).
Use handlerArgs
directly as one of the function arguments for the `Call() invocation.