-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add middleware support as discussed in #293 #294
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv | |
* [Registered URLs](#registered-urls) | ||
* [Walking Routes](#walking-routes) | ||
* [Graceful Shutdown](#graceful-shutdown) | ||
* [Middleware](#middleware) | ||
* [Full Example](#full-example) | ||
|
||
--- | ||
|
@@ -447,6 +448,82 @@ func main() { | |
} | ||
``` | ||
|
||
### Middleware | ||
|
||
Since **vX.Y.Z**, mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed if a | ||
match is found (including subrouters). Middlewares are defined using the de facto standard type: | ||
|
||
```go | ||
type MiddlewareFunc func(http.Handler) http.Handler | ||
``` | ||
|
||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another use of parens that could be avoided. |
||
|
||
A very basic middleware which logs the URI of the request being handled could be written as: | ||
|
||
```go | ||
func simpleMw(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// Do stuff here | ||
log.Println(r.RequestURI) | ||
// Call the next handler, which can be another middleware in the chain, or the final handler. | ||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
``` | ||
|
||
Middlewares can be added to a router using `Router.AddMiddlewareFunc()`: | ||
|
||
```go | ||
r := mux.NewRouter() | ||
r.HandleFunc("/", handler) | ||
r.AddMiddleware(simpleMw) | ||
``` | ||
|
||
A more complex authentication middleware, which maps session token to users, could be written as: | ||
|
||
```go | ||
// Define our struct | ||
type authenticationMiddleware struct { | ||
tokenUsers map[string]string | ||
} | ||
|
||
// Initialize it somewhere | ||
func (amw *authenticationMiddleware) Populate() { | ||
amw.tokenUsers["00000000"] = "user0" | ||
amw.tokenUsers["aaaaaaaa"] = "userA" | ||
amw.tokenUsers["05f717e5"] = "randomUser" | ||
amw.tokenUsers["deadbeef"] = "user0" | ||
} | ||
|
||
// Middleware function, which will be called for each request | ||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
token := r.Header.Get("X-Session-Token") | ||
|
||
if user, found := amw.tokenUsers[token]; found { | ||
// We found the token in our map | ||
log.Printf("Authenticated user %s\n", user) | ||
next.ServeHTTP(w, r) | ||
} else { | ||
http.Error(w, "Forbidden", 403) | ||
} | ||
}) | ||
} | ||
``` | ||
|
||
```go | ||
r := mux.NewRouter() | ||
r.HandleFunc("/", handler) | ||
|
||
amw := authenticationMiddleware{} | ||
amw.Populate() | ||
|
||
r.AddMiddlewareFunc(amw.Middleware) | ||
``` | ||
|
||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added, but reworded "make sure" to "should", as some particular scenarios may apply and it is not a mux requirement. |
||
|
||
## Full Example | ||
|
||
Here's a complete, runnable example of a small `mux` based server: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -238,5 +238,70 @@ as well: | |
url, err := r.Get("article").URL("subdomain", "news", | ||
"category", "technology", | ||
"id", "42") | ||
|
||
Since **vX.Y.Z**, mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed if a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update to align with the README (Note to self: use tooling to make this automatic...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the version thingie. I'm not sure if its an important enough information to be in the readme. Feel free to re-add it if you think it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good move. I think it's fair to assume that the documentation reflects There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yikes, I totally missed that one :/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ll take a look today. The website doesn’t do Markdown, which is a bit of a pain. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, thanks guys :) |
||
match is found (including subrouters). Middlewares are defined using the de facto standard type: | ||
|
||
type MiddlewareFunc func(http.Handler) http.Handler | ||
|
||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). | ||
|
||
A very basic middleware which logs the URI of the request being handled could be written as: | ||
|
||
func simpleMw(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// Do stuff here | ||
log.Println(r.RequestURI) | ||
// Call the next handler, which can be another middleware in the chain, or the final handler. | ||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
Middlewares can be added to a router using `Router.AddMiddlewareFunc()`: | ||
|
||
r := mux.NewRouter() | ||
r.HandleFunc("/", handler) | ||
r.AddMiddleware(simpleMw) | ||
|
||
A more complex authentication middleware, which maps session token to users, could be written as: | ||
|
||
// Define our struct | ||
type authenticationMiddleware struct { | ||
tokenUsers map[string]string | ||
} | ||
|
||
// Initialize it somewhere | ||
func (amw *authenticationMiddleware) Populate() { | ||
amw.tokenUsers["00000000"] = "user0" | ||
amw.tokenUsers["aaaaaaaa"] = "userA" | ||
amw.tokenUsers["05f717e5"] = "randomUser" | ||
amw.tokenUsers["deadbeef"] = "user0" | ||
} | ||
|
||
// Middleware function, which will be called for each request | ||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
token := r.Header.Get("X-Session-Token") | ||
|
||
if user, found := amw.tokenUsers[token]; found { | ||
// We found the token in our map | ||
log.Printf("Authenticated user %s\n", user) | ||
next.ServeHTTP(w, r) | ||
} else { | ||
http.Error(w, "Forbidden", 403) | ||
} | ||
}) | ||
} | ||
|
||
r := mux.NewRouter() | ||
r.HandleFunc("/", handler) | ||
|
||
amw := authenticationMiddleware{} | ||
amw.Populate() | ||
|
||
r.AddMiddlewareFunc(amw.Middleware) | ||
|
||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. | ||
|
||
*/ | ||
package mux |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package mux | ||
|
||
import "net/http" | ||
|
||
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. | ||
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed | ||
// to it, and then calls the handler passed as parameter to the MiddlewareFunc. | ||
type MiddlewareFunc func(http.Handler) http.Handler | ||
|
||
// middleware interface is anything which implements a MiddlewareFunc named Middleware. | ||
type middleware interface { | ||
Middleware(handler http.Handler) http.Handler | ||
} | ||
|
||
// MiddlewareFunc also implements the Middleware interface. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update this? As we don't export the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean the comment or the implementation? The implementation needs to be there in order to store There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just the comment 👌🏼 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { | ||
return mw(handler) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// AddMiddlewareFunc appends a MiddlewareFunc to the chain. | ||
func (r *Router) AddMiddlewareFunc(mwf MiddlewareFunc) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'd be OK with just calling this function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let’s just stick with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. I've renamed the internal |
||
r.middlewares = append(r.middlewares, mwf) | ||
} | ||
|
||
// addMiddleware appends a middleware to the chain. | ||
func (r *Router) addMiddleware(mw middleware) { | ||
r.middlewares = append(r.middlewares, mw) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, I forgot to mention I did not add the link because most handler don't actually implement
mux.Middleware
, as they require additional parameters. We could circunvent this using closures, but I think it would confuse some users instead of adding clarity. Maybe we could rework some handlers to make them comply with the interface?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe instead of changing them we could add a
XXXXMiddleware
function for each one?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, of course. We should not break existing handlers, so maybe wrapping them somehow, or refactoring the core logic into a different, private function and exposing both the actual and the Middleware flavours of it. I can give it a shot if you think it's worth it.