Skip to content

Commit 53c1911

Browse files
Roberto Santallaelithrar
authored andcommitted
[feat] Add middleware support as discussed in #293 (#294)
* mux.Router now has a `Use` method that allows you to add middleware to request processing.
1 parent 5bbbb5b commit 53c1911

File tree

5 files changed

+519
-0
lines changed

5 files changed

+519
-0
lines changed

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
2828
* [Registered URLs](#registered-urls)
2929
* [Walking Routes](#walking-routes)
3030
* [Graceful Shutdown](#graceful-shutdown)
31+
* [Middleware](#middleware)
3132
* [Full Example](#full-example)
3233

3334
---
@@ -447,6 +448,86 @@ func main() {
447448
}
448449
```
449450

451+
### Middleware
452+
453+
Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
454+
Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
455+
456+
Mux middlewares are defined using the de facto standard type:
457+
458+
```go
459+
type MiddlewareFunc func(http.Handler) http.Handler
460+
```
461+
462+
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. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
463+
464+
A very basic middleware which logs the URI of the request being handled could be written as:
465+
466+
```go
467+
func simpleMw(next http.Handler) http.Handler {
468+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
469+
// Do stuff here
470+
log.Println(r.RequestURI)
471+
// Call the next handler, which can be another middleware in the chain, or the final handler.
472+
next.ServeHTTP(w, r)
473+
})
474+
}
475+
```
476+
477+
Middlewares can be added to a router using `Router.AddMiddlewareFunc()`:
478+
479+
```go
480+
r := mux.NewRouter()
481+
r.HandleFunc("/", handler)
482+
r.AddMiddleware(simpleMw)
483+
```
484+
485+
A more complex authentication middleware, which maps session token to users, could be written as:
486+
487+
```go
488+
// Define our struct
489+
type authenticationMiddleware struct {
490+
tokenUsers map[string]string
491+
}
492+
493+
// Initialize it somewhere
494+
func (amw *authenticationMiddleware) Populate() {
495+
amw.tokenUsers["00000000"] = "user0"
496+
amw.tokenUsers["aaaaaaaa"] = "userA"
497+
amw.tokenUsers["05f717e5"] = "randomUser"
498+
amw.tokenUsers["deadbeef"] = "user0"
499+
}
500+
501+
// Middleware function, which will be called for each request
502+
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
503+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
504+
token := r.Header.Get("X-Session-Token")
505+
506+
if user, found := amw.tokenUsers[token]; found {
507+
// We found the token in our map
508+
log.Printf("Authenticated user %s\n", user)
509+
// Pass down the request to the next middleware (or final handler)
510+
next.ServeHTTP(w, r)
511+
} else {
512+
// Write an error and stop the handler chain
513+
http.Error(w, "Forbidden", 403)
514+
}
515+
})
516+
}
517+
```
518+
519+
```go
520+
r := mux.NewRouter()
521+
r.HandleFunc("/", handler)
522+
523+
amw := authenticationMiddleware{}
524+
amw.Populate()
525+
526+
r.AddMiddlewareFunc(amw.Middleware)
527+
```
528+
529+
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. Middlewares *should* write to `ResponseWriter` if they *are* going to terminate the request, and they *should not* write to `ResponseWriter` if they *are not* going to terminate it.
530+
450531
## Full Example
451532

452533
Here's a complete, runnable example of a small `mux` based server:

doc.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,70 @@ as well:
238238
url, err := r.Get("article").URL("subdomain", "news",
239239
"category", "technology",
240240
"id", "42")
241+
242+
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
243+
match is found (including subrouters). Middlewares are defined using the de facto standard type:
244+
245+
type MiddlewareFunc func(http.Handler) http.Handler
246+
247+
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).
248+
249+
A very basic middleware which logs the URI of the request being handled could be written as:
250+
251+
func simpleMw(next http.Handler) http.Handler {
252+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
253+
// Do stuff here
254+
log.Println(r.RequestURI)
255+
// Call the next handler, which can be another middleware in the chain, or the final handler.
256+
next.ServeHTTP(w, r)
257+
})
258+
}
259+
260+
Middlewares can be added to a router using `Router.Use()`:
261+
262+
r := mux.NewRouter()
263+
r.HandleFunc("/", handler)
264+
r.AddMiddleware(simpleMw)
265+
266+
A more complex authentication middleware, which maps session token to users, could be written as:
267+
268+
// Define our struct
269+
type authenticationMiddleware struct {
270+
tokenUsers map[string]string
271+
}
272+
273+
// Initialize it somewhere
274+
func (amw *authenticationMiddleware) Populate() {
275+
amw.tokenUsers["00000000"] = "user0"
276+
amw.tokenUsers["aaaaaaaa"] = "userA"
277+
amw.tokenUsers["05f717e5"] = "randomUser"
278+
amw.tokenUsers["deadbeef"] = "user0"
279+
}
280+
281+
// Middleware function, which will be called for each request
282+
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
283+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
284+
token := r.Header.Get("X-Session-Token")
285+
286+
if user, found := amw.tokenUsers[token]; found {
287+
// We found the token in our map
288+
log.Printf("Authenticated user %s\n", user)
289+
next.ServeHTTP(w, r)
290+
} else {
291+
http.Error(w, "Forbidden", 403)
292+
}
293+
})
294+
}
295+
296+
r := mux.NewRouter()
297+
r.HandleFunc("/", handler)
298+
299+
amw := authenticationMiddleware{}
300+
amw.Populate()
301+
302+
r.Use(amw.Middleware)
303+
304+
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.
305+
241306
*/
242307
package mux

middleware.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package mux
2+
3+
import "net/http"
4+
5+
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
6+
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
7+
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
8+
type MiddlewareFunc func(http.Handler) http.Handler
9+
10+
// middleware interface is anything which implements a MiddlewareFunc named Middleware.
11+
type middleware interface {
12+
Middleware(handler http.Handler) http.Handler
13+
}
14+
15+
// MiddlewareFunc also implements the middleware interface.
16+
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
17+
return mw(handler)
18+
}
19+
20+
// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
21+
func (r *Router) Use(mwf MiddlewareFunc) {
22+
r.middlewares = append(r.middlewares, mwf)
23+
}
24+
25+
// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
26+
func (r *Router) useInterface(mw middleware) {
27+
r.middlewares = append(r.middlewares, mw)
28+
}

0 commit comments

Comments
 (0)