在请求完成后,Negroni继续致电其他处理程序

时间:2018-05-03 00:19:50

标签: go gorilla negroni

我在Go中的Web应用程序(使用Gorilla negroniGET /favicon.ico GET /files GET /files/index.html GET /files/favicon.ico )有大约20个处理程序,根据应该应用的中间件功能分为三组。具体做法是:

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

    GET   /
    GET   /login
    POST  /login
    GET   /auth-configuration
    GET   /service-status
    
  • 第2组:仅应具有CORS中间件的请求,不进行身份验证:

    GET   /articles
    POST  /articles
    PUT   /articles/etc
    PATCH /articles/etc
    
  • 第3组:应该同时应用CORS和身份验证中间件的请求:

    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
    }
    

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

(*Negroni).ServeHTTP

我认为这是正确的,基于"路由特定中间件" Negroni's documentation中的部分,其中包含:

  

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

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

如果路径不存在,则会调用Gorilla默认NotFoundHandler 3次(每个mux.Router一个)。

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

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

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

1 个答案:

答案 0 :(得分: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实例,而是只创建一个应用于所需路由的实例。