28
Chaining middleware in Go using justinas/alice package
We can make a couple of improvements to the simple API we created that included a couple of middleware wrapping functions.
- We're going to split the project into separate files.
- We're going to use a 3rd party package to chain the middleware functions using a popular community package called alice.
- create a new project and run
go mid init <name-of-your-project>
- Run
go get github.com/justinas/alice
on the cli. - Create
main.go
,handlers.go
&middlewares.go
files.
In the handlers.go file we want to place the following code.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Lesson struct {
Title string
Summary string
}
func handle(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// create a variable of our defined struct type Lesson
var tempLesson Lesson
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&tempLesson)
if err != nil {
panic(err)
}
defer r.Body.Close()
fmt.Printf("Title: %s. Summary: %s\n", tempLesson.Title, tempLesson.Summary)
w.WriteHeader(http.StatusCreated)
w.Write([]byte("201 - Created"))
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("405 - Method Not Allowed"))
}
}
In the middlewares.go file we can add the following code.
package main
import (
"log"
"net/http"
"strconv"
"time"
)
func filterContentType(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Inside filterContentType middleware, before the request is handled")
// filter requests by mime type
if r.Header.Get("Content-Type") != "application/json" {
w.WriteHeader(http.StatusUnsupportedMediaType)
w.Write([]byte("415 - Unsupported Media Type. JSON type expected"))
return
}
// handle the request
handler.ServeHTTP(w, r)
log.Println("Inside filterContentType middleware, after the request was handled")
})
}
func setServerTimeCookie(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Inside setServerTimeCookie middleware, before the request is handled")
// handles the request
handler.ServeHTTP(w, r)
cookie := http.Cookie{Name: "Server Time(UTC)", Value: strconv.FormatInt(time.Now().Unix(), 10)}
http.SetCookie(w, &cookie)
log.Println("Inside setServerTimeCookie middleware, after the request is handled")
})
}
in main.go we need to add the following
package main
// chaing middleware with alice.
// here we will use the alice package to demo chainging middleware
// around our original or main handler.
import (
"log"
"net/http"
"github.com/justinas/alice"
)
func main() {
port := ":8080"
originalHandler := http.HandlerFunc(handle)
chain := alice.New(filterContentType, setServerTimeCookie).Then(originalHandler)
http.Handle("/lesson", chain)
log.Fatal(http.ListenAndServe(port, nil))
}
As you can see our refactor has added a 3rd party dependency but increased readbility. The fluent API of the alice
package makes for clear and readable code when used.
Splitting the project into multiple files here is trivial as we have a tiny codebase and it is purely for demo purposes, however it is reasonably good, common practice to split your files in Go projects. This lowers the cognitive load from each file on the reader.
28