【问题标题】:Negroni continues to call other handlers after request is completed请求完成后,Negroni 继续调用其他处理程序
【发布时间】:2018-05-03 10:56:41
【问题描述】:

我在 Go 中的 Web 应用程序(使用 Gorilla muxnegroni)有大约 20 个处理程序,根据应该应用的中间件函数分为三组。具体来说:

  • 第 1 组:静态请求(根本没有中间件)

    GET   /favicon.ico
    GET   /files
    GET   /files/index.html
    GET   /files/favicon.ico
    
  • 第 2 组:应该只有 CORS 中间件的请求,没有身份验证:

    GET   /
    GET   /login
    POST  /login
    GET   /auth-configuration
    GET   /service-status
    
  • 第 3 组:应同时应用 CORS 和身份验证中间件的请求:

    GET   /articles
    POST  /articles
    PUT   /articles/etc
    PATCH /articles/etc
    

这是我设置 HTTP 服务器的代码:

func run() {

    negroniStack := setUpNegroni()

    bindAddr := // ...

    http.ListenAndServe(bindAddr, negroniStack)
}

func setUpNegroni() negroni.Negroni {

    negroniStack := negroni.Negroni{}

    staticNegroni := setUpRoutesAndMiddlewareForStaticRequests()
    loginNegroni  := setUpRoutesAndMiddlewareForLogin()
    serviceNegroni = setUpRoutesAndMiddlewareForService()

    negroniStack.UseHandler(&staticNegroni)
    negroniStack.UseHandler(&loginNegroni)
    negroniStack.UseHandler(&serviceNegroni)

    return negroniStack
}

func setUpRoutesAndMiddlewareForStaticRequests() negroni.Negroni {

    staticNegroni := negroni.Negroni{}
    staticRouter := mux.NewRouter()

    staticRouter.PathPrefix("/files").HandlerFunc(staticHandler)
    staticRouter.Path("/favicon.ico").HandlerFunc(staticHandler)

    staticNegroni.UseHandler(staticRouter)
    return staticNegroni
}

func setUpRoutesAndMiddlewareForLogin() negroni.Negroni {

    authNegroni := negroni.Negroni{}

    corsMiddleware := cors.New(cors.Options{
        AllowedMethods:     []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"},
        AllowCredentials:   true,
        OptionsPassthrough: false,
    })

    authNegroni.Use(corsMiddleware)

    authRouter := mux.NewRouter()

    authRouter.HandleFunc("/login", HandlePostAuth).Methods("POST")
    authRouter.HandleFunc("/login", HandleGetAuth) // GET

    authNegroni.UseHandler(authRouter)

    return authNegroni
}

func setUpRoutesAndMiddlewareForService() negroni.Negroni {

    serviceNegroni := negroni.Negroni{}

    corsMiddleware := cors.New(cors.Options{
        AllowedMethods:     []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"},
        AllowCredentials:   true,
        OptionsPassthrough: false,
    })
    serviceNegroni.Use(corsMiddleware)

    serviceNegroni.UseFunc(jwtMiddleware)

    serviceRouter := mux.NewRouter()
    serviceRouter.HandleFunc("/articles", HandleGetArticles).Methods("GET")
    serviceRouter.HandleFunc("/articles", HandlePostArticles).Methods("POST")
    // etc

    serviceNegroni.UseHandler(serviceRouter)

    return serviceNegroni
}

根据Negroni's documentation 中的“路由特定中间件”部分,我认为这是正确的:

如果您有一组需要执行特定中间件的路由,您可以简单地创建一个新的 Negroni 实例并将其用作您的路由处理程序。

但是,当我发出请求并使用调试器时,我看到(*Negroni).ServeHTTP 被多次调用。例如,如果我请求GET /favicon.ico,则正确调用staticHandler 函数并调用WriteHeader(200),但之后它会调用下一个mux.Router,后者调用WriteHeader(404),它会在终端中打印出警告,因为标头被写入两次 (http: multiple response.WriteHeader calls)

如果是针对不存在的路由,则调用 Gorilla 默认的 NotFoundHandler 3 次(每个 mux.Router 调用一次)。

如何让 Negroni 在请求完成后停止调用其他处理程序?

...如果我的 Negroni 实例配置错误,为什么它不在初始化期间执行检查以警告我配置无效?

我的理解是negroni.UseUseFunc是用于设置中间件(每个请求都会调用),而UseHandler是设置终端处理程序(每个请求只调用1个) ,或回退到 404)。如果我正确理解这种情况,那么出于某种原因,它会将我的终端处理程序视为中间件。

【问题讨论】:

  • 这里有完整的路线列表吗?你有像“/{category}”这样的路线吗?
  • @vitr 我可以使用此处发布的代码重现该问题。

标签: go gorilla negroni


【解决方案1】:

来自UseHandler 文档 (https://godoc.org/github.com/urfave/negroni#Negroni.UseHandler)

UseHandler 将 http.Handler 添加到中间件堆栈中。 处理程序按照它们添加到 Negroni 的顺序被调用。

看来您在这里看到的是预期的行为。

您基本上是在创建不同的 negroni 实例并将它们链接起来,因此您的最终 negroniStack 本身就是一个中间件,它将执行您添加的其他中间件。

我相信您想要做的是使用实际路由器创建路由,然后将适当的中间件(使用 negroni)添加到每个路由。

如果您查看您从文档链接的示例,这就是他们在该部分中所做的 (https://github.com/urfave/negroni#route-specific-middleware)。

router.PathPrefix("/admin").Handler(negroni.New(
   Middleware1,
   Middleware2,
   negroni.Wrap(adminRoutes),
))

请注意,它们不是嵌套 negroni 实例,而是仅创建一个应用于所需路由的实例。

【讨论】:

  • 我没有使用PathPrefixSubrouter - 所以我必须为每条路线重复negroni.New( mw1, mw2, negroni.Wrap( HandlerNameHere ) )(其中20+)。我不喜欢那种设计——它不是很干。没有更好的办法吗?
  • @Dai:您肯定需要重新构建构建它的方式,不重复代码的一种选择是创建一个函数来创建 negroni 并包装您正在创建的任何处理程序,然后调用它就像 wrapWithCors(myHandler)... 只是一个例子
  • 是的,这会起作用——尽管它仍然感觉不对。对我来说,将路由器(及其所有已注册的路由)包装在中间件层中比将其与每个路由关联更有意义 - 但我看不出如何,因为无法“合并”Router对象,除非它们在层次结构中。
  • 这最终奏效了——虽然我没有在router.Handler()方法中调用negroni.New( mw1, mw2, negroni.Wrap(ActualHandler)),而是使用了另一个函数,但原理是一样的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-18
  • 2017-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多