doujiao3998 2018-02-05 16:57
浏览 34
已采纳

Golang Api只匹配最后一条路线

I have a golang api application. I've defined a set of routes and handlers. However, the mux router only ever returns the last route.

When I request /api/info I get this in my logging:

9:0:38 app | 2018/02/05 09:00:38 GET /api/info Users Create 308.132µs

Why is that routing to the wrong route?

routing package:

// NewRouter establishes the root application router
func NewRouter(context *config.ApplicationContext, routes Routes, notFoundHandler http.HandlerFunc) *mux.Router {
    router := mux.NewRouter()

    router.NotFoundHandler = notFoundHandler

    for _, route := range routes {
        router.
            PathPrefix("/api").
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            // TODO: fix HandlerFunc. Right now, it is overriding previous routes and setting a single handler for all
            // this means that the last route is the only router with a handler
            HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
            })

    }

    return router
}

func logRoute(inner ContextHandlerFunc, name string) ContextHandlerFunc {
    return func(c *config.ApplicationContext, w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        inner(c, w, r)

        log.Printf(
            "%s\t%s\t%s\t%s",
            r.Method,
            r.RequestURI,
            name,
            time.Since(start),
        )
    }
}

func setJSONHeader(inner ContextHandlerFunc) ContextHandlerFunc {
    return func(c *config.ApplicationContext, w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        inner(c, w, r)
    }
}

main package:

var context = config.ApplicationContext{
    Database: database.NewDatabase().Store,
}

var routes = router.Routes{
    router.Route{"Info", "GET", "/info", handlers.InfoShow},
    router.Route{"Users Create", "POST", "/users/create", handlers.UsersCreate},
}

func main() {    
    notFoundHandler := handlers.Errors404
    router := router.NewRouter(&context, routes, notFoundHandler)

    port := os.Getenv("PORT")

    log.Fatal(http.ListenAndServe(":"+port, router))
}

If I visit /api/info it will attempt to call a POST to /users/create. However, if I remove the second route, it will correctly route to the InfoShow handler.

Why is mux overriding the first route? I'm fairly certain there's something wrong with

HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
}) 

but I'm not sure why that would cause it to map over the first route.

Ideas?

  • 写回答

1条回答 默认 最新

  • dsyk33753 2018-02-05 18:56
    关注

    Reading through your code and gorilla/mux, I think I know the issue. You're using the for loop variable route, and specifically its field HanderFunc, in the function literal, but because of how function literals work, the value of that field is not evaluated until the that function literal is called. In Go, the second variable in a range loop is reused on each iteration, rather than created anew, and so after the for loop, if it's still in scope of anything (like your function literal), it will contain the value of the last loop iteration. Here's an example of what I mean:

    https://play.golang.org/p/Xx62tuwhtgG

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var funcs []func()
        ints := []int{1, 2, 3, 4, 5}
    
        // How you're doing it
        for i, a := range ints {
            fmt.Printf("Loop i: %v, a: %v
    ", i, a)
            funcs = append(funcs, func() {
                fmt.Printf("Lambda i: %v, a: %v
    ", i, a)
            })
        }
    
        for _, f := range funcs {
            f()
        }
    
        fmt.Println("-------------")
    
        // How you *should* do it
        funcs = nil
        for i, a := range ints {
            i := i
            a := a
            fmt.Printf("Loop i: %v, a: %v
    ", i, a)
            funcs = append(funcs, func() {
                fmt.Printf("Lambda i: %v, a: %v
    ", i, a)
            })
        }
    
        for _, f := range funcs {
            f()
        }
    }
    

    In the first example, i and a are being reused on each loop iteration, and aren't evaluated for their values in the lambda (function literal) until that lambda is actually called (by the funcs loop). To fix that, you can shadow a and i by redeclaring them inside the scope of the loop iteration (but outside the lambda's scope). This makes a separate copy for each iteration, to avoid issues with reuse of the same variable.

    Specifically for your code, if you change your code to the following, it should work:

    for _, route := range routes {
        route := route // make a copy of the route for use in the lambda
        // or alternatively, make scoped vars for the name and handler func
    
        router.
            PathPrefix("/api").
            Methods(route.Method).
            Path(route.Pattern).
            Name(route.Name).
            // TODO: fix HandlerFunc. Right now, it is overriding previous routes and setting a single handler for all
            // this means that the last route is the only router with a handler
            HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                logRoute(setJSONHeader(route.HandlerFunc), route.Name)(context, w, r)
            })
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 多址通信方式的抗噪声性能和系统容量对比
  • ¥15 winform的chart曲线生成时有凸起
  • ¥15 msix packaging tool打包问题
  • ¥15 finalshell节点的搭建代码和那个端口代码教程
  • ¥15 Centos / PETSc / PETGEM
  • ¥15 centos7.9 IPv6端口telnet和端口监控问题
  • ¥20 完全没有学习过GAN,看了CSDN的一篇文章,里面有代码但是完全不知道如何操作
  • ¥15 使用ue5插件narrative时如何切换关卡也保存叙事任务记录
  • ¥20 海浪数据 南海地区海况数据,波浪数据
  • ¥20 软件测试决策法疑问求解答