3

我正在尝试将参数动态传递给 URL 路由处理程序函数。我认为可以使用反射包将值映射从 URL 转换为具有一个参数的函数,该参数恰好是匿名结构。我已经创建了要传递给处理函数的结构,但它最终成为了一个指向该结构的指针。我认为,如果我更改处理函数的签名以期望指针,则创建的结构最终会成为指向指针的指针。无论如何,这是代码(恐慌如下):

链接: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)
}

恐慌:

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)
4

2 回答 2

3

在您的 reflect.Value 对象上调用 Elem()。

引用反射法则文章:

为了得到 p 指向的内容,我们调用 Value 的 Elem 方法,它通过指针间接

于 2013-03-28T23:14:45.143 回答
0

请注意,这reflect.New()会创建一个指向您传递给它的类型的值的指针。所以在下面一行:

handlerArgs := reflect.New(t.In(0)).Interface()

handlerArgs将是指向类型结构的指针t.In(0)。您将需要取消引用该指针以具有适合传递给函数的值。

我建议如下:

  1. 为实际的结构值做hanlerArgs一个:*reflect.Value

    handlerArgs := reflect.New(t.In(0)).Elem()
    
  2. mapToStruct使用这样的 a而*reflect.Value不是interface{}持有指向结构的指针(毕竟它是反射代码的助手)。

  3. 直接handlerArgs用作 `Call() 调用的函数参数之一。

于 2013-03-29T01:32:29.753 回答