3

パラメータを URL ルート ハンドラ関数に動的に渡そうとしています。リフレクション パッケージを使用して、値のマップを URL から、たまたま匿名構造体である 1 つのパラメーターを持つ関数に変換できると考えました。ハンドラー関数に渡す構造体を作成するところまで行きましたが、構造体へのポインターになってしまいます。ハンドラー関数のシグネチャをポインターを期待するように変更すると、作成された構造体がポインターへのポインターになってしまうと思います。とにかく、これがコードです(パニックが続きます):

リンク: 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() を呼び出します。

The Laws of Reflection の記事からの引用:

p が指すものを取得するには、Value の Elem メソッドを呼び出します。これは、ポインターを介して間接的に行います。

于 2013-03-28T23:14:45.143 に答える
0

reflect.New()渡した型の値へのポインタを作成することに注意してください。したがって、次の行で:

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

handlerArgs型の構造体へのポインタになりますt.In(0)。関数に渡すのに適した値を持つには、そのポインターを逆参照する必要があります。

次のことをお勧めします。

  1. 実際の構造体値のhanlerArgsaを作成します。*reflect.Value

    handlerArgs := reflect.New(t.In(0)).Elem()
    
  2. 構造体へのポインターを保持する代わりに、そのmapToStructようなものを取得します (結局のところ、これはリフレクション コードのヘルパーです)。*reflect.Valueinterface{}

  3. handlerArgs`Call() 呼び出しの関数引数の 1 つとして直接使用します。

于 2013-03-29T01:32:29.753 に答える