Golang Api 只匹配最后一个路由

Posted

技术标签:

【中文标题】Golang Api 只匹配最后一个路由【英文标题】:Golang Api Only Matches Last Route 【发布时间】:2018-02-05 16:57:48 【问题描述】:

我有一个 golang api 应用程序。我已经定义了一组路由和处理程序。但是,多路复用路由器只返回最后一条路由。

当我请求 /api/info 时,我在日志中得到了这个:

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

为什么路由到错误的路由?

路由包:

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

主包:

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

如果我访问/api/info,它将尝试向/users/create 调用POST。但是,如果我删除第二条路由,它将正确路由到 InfoShow 处理程序。

为什么 mux 会覆盖第一条路由?我很确定

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

但我不确定为什么会导致它映射到第一条路线。

想法?

【问题讨论】:

请说明您使用的是什么mux 包。 @leafbebop 我正在使用最新的github.com/gorilla/mux 不相关,但这是一种糟糕的做法:router := router.NewRouter(&context, routes, notFoundHandler)。您正在使用 router 变量隐藏 router 包。以不同的方式命名它们。作为一个实践问题,建议不要以某人自然会从该包中命名该类型的名称来命名您的包。使用您在 OP 中指定的 routing 名称而不是路由器。将减少混乱的代码。 谢谢。 golang 新手。那是无意的,我将重命名它。我要做的就是将数据库传递给处理程序。 Go 迫使我将所有内容分成多个包,但从根本上说,这些部分是一个单独的包,所以我很难找到一个好的结构。 如果需要,您可以将它们全部放在同一个包中。将它们分开可能是一种很好的做法,因为它可以让您更好地隔离关注点,但是在 Go 中没有什么强制您将代码分成包,除非您希望获得单独命名空间的文档优势。跨度> 【参考方案1】:

通读您的代码和 gorilla/mux,我想我知道问题所在。您在函数文字中使用了 for 循环变量 route,特别是其字段 HanderFunc,但由于函数文字的工作方式,该字段的 value 直到该函数才被评估字面被称为。在 Go 中,范围循环中的第二个变量在每次迭代时重用,而不是重新创建,因此在 for 循环之后,如果它仍然在任何东西的范围内(比如你的函数字面量),它将包含 last 循环迭代的值。这是我的意思的一个例子:

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

package main

import (
    "fmt"
)

func main() 
    var funcs []func()
    ints := []int1, 2, 3, 4, 5

    // How you're doing it
    for i, a := range ints 
        fmt.Printf("Loop i: %v, a: %v\n", i, a)
        funcs = append(funcs, func() 
            fmt.Printf("Lambda i: %v, a: %v\n", 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\n", i, a)
        funcs = append(funcs, func() 
            fmt.Printf("Lambda i: %v, a: %v\n", i, a)
        )
    

    for _, f := range funcs 
        f()
    

在第一个示例中,ia 在每次循环迭代中都被重用,并且不会在 lambda(函数字面量)中对它们的值进行评估,直到该 lambda 真正被调用(通过funcs 循环)。要解决这个问题,您可以通过在循环迭代的范围内(但在 lambda 的范围之外)重新声明 ai。这会为每次迭代制作一个单独的副本,以避免重复使用相同变量的问题。

特别是对于您的代码,如果您将代码更改为以下内容,它应该可以工作:

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

【讨论】:

啊,这很有道理!谢谢你的例子。我稍后会对此进行测试,但根据您的游乐场,我认为这是绝对正确的。我想知道这种迭代设置一堆路线的模式是否真的是一个好方法。 我见过更糟糕的情况。它允许您在 main 中指定您的路由,同时实际处理将它们附加到路由包中的路由器。这是一个很好的设计,因为没有真正的理由让您的路由包管理您的路由关联。 当您在循环中创建一堆回调函数闭包时,这也是 javascript 中非常常见的问题。看起来在 Go 中应用了相同的解决方案:使用另一个函数(命名或匿名)来构建回调函数并强制对循环变量进行评估而不是关闭。 是的,您可以将循环内的整个部分移动到另一个接受router(作为指针)和route(作为结构)的命名函数中,然后简单地调用该函数循环内。由于route 参数将作为一个值传递,它会自动被复制并且重用问题将消失。

以上是关于Golang Api 只匹配最后一个路由的主要内容,如果未能解决你的问题,请参考以下文章

Golang Web入门:如何实现一个RESTful风格的路由

Golang Web入门:如何实现一个RESTful风格的路由

RabbitMQ 部分API解析

Golang 正则表达式匹配字符串仅对使用 OR 运算符分隔的字符串列表中的最后一个子字符串失败 |

在 Golang 和 MongoDB 中将路由拆分为单独的包

路由基础-静态路由