My web-application in Go (using Gorilla mux
and negroni
) has about 20 handlers split into three groups based on what Middleware functions should be applied. Specifically:
Group 1: Static requests (no middleware at all)
GET /favicon.ico GET /files GET /files/index.html GET /files/favicon.ico
Group 2: Requests that should have CORS middleware only, no authentication:
GET / GET /login POST /login GET /auth-configuration GET /service-status
Group 3: Requests that should have both CORS and authentication middleware applied:
GET /articles POST /articles PUT /articles/etc PATCH /articles/etc
This is my code that sets-up the HTTP server:
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
}
I believe this is correct based on the "Route Specific Middleware" section in Negroni's documentation where it says:
If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler.
However, when I make requests and use the debugger, I see that (*Negroni).ServeHTTP
is called multiple times. For example, if I request GET /favicon.ico
then the staticHandler
function is called correctly and calls WriteHeader(200)
, but after that it then calls into the next mux.Router
which calls WriteHeader(404)
which prints out a warning in the terminal because the header was written twice (http: multiple response.WriteHeader calls
)
If it's for a route that doesn't exist then the Gorilla default NotFoundHandler
is invoked 3 times (one for each mux.Router
).
How do I get Negroni to stop invoking other handlers after the request was completed?
...and if I have misconfigured my Negroni instance, why doesn't it perform checks during initialization to warn me about an invalid configuration?
My understanding is that negroni.Use
and UseFunc
are for setting-up middleware (which are all invoked for every request), while UseHandler
is to set-up the terminal handler (only 1 is invoked for each request, or fallback to 404). If I understand the situation correctly then for some reason it's treating my terminal handlers as middlewares.