How to do it...

The following steps cover the writing and running of your application:

  1. From your Terminal or console application, create a new directory called ~/projects/go-programming-cookbook/chapter8/middlewareand navigate to this directory.
  2. Run the following command:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter8/middleware

You should see a file called go.mod that contains the following:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter8/middleware    
  1. Copy the tests from ~/projects/go-programming-cookbook-original/chapter8/middleware, or use this as an exercise to write some of your own code!
  2. Create a file called middleware.go with the following contents:
        package middleware

import (
"log"
"net/http"
"time"
)

// Middleware is what all middleware functions will return
type Middleware func(http.HandlerFunc) http.HandlerFunc

// ApplyMiddleware will apply all middleware, the last
// arguments will be the
// outer wrap for context passing purposes
func ApplyMiddleware(h http.HandlerFunc, middleware
...Middleware) http.HandlerFunc {
applied := h
for _, m := range middleware {
applied = m(applied)
}
return applied
}

// Logger logs requests, this will use an id passed in via
// SetID()
func Logger(l *log.Logger) Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
l.Printf("started request to %s with id %s", r.URL,
GetID(r.Context()))
next(w, r)
l.Printf("completed request to %s with id %s in
%s", r.URL, GetID(r.Context()), time.Since(start))
}
}
}
  1. Create a file called context.go with the following contents:
        package middleware

import (
"context"
"net/http"
"strconv"
)

// ContextID is our type to retrieve our context
// objects
type ContextID int

// ID is the only ID we've defined
const ID ContextID = 0

// SetID updates context with the id then
// increments it
func SetID(start int64) Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ID,
strconv.FormatInt(start, 10))
start++
r = r.WithContext(ctx)
next(w, r)
}
}
}

// GetID grabs an ID from a context if set
// otherwise it returns an empty string
func GetID(ctx context.Context) string {
if val, ok := ctx.Value(ID).(string); ok {
return val
}
return ""
}
  1. Create a file called handler.go with the following contents:
        package middleware

import (
"net/http"
)

// Handler is very basic
func Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
}
  1. Create a new directory named example and navigate to it.
  2. Create a file called main.go with the following contents:
        package main

import (
"fmt"
"log"
"net/http"
"os"

"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter8/middleware"
)

func main() {
// We apply from bottom up
h := middleware.ApplyMiddleware(
middleware.Handler,
middleware.Logger(log.New(os.Stdout, "", 0)),
middleware.SetID(100),
)
http.HandleFunc("/", h)
fmt.Println("Listening on port :3333")
err := http.ListenAndServe(":3333", nil)
panic(err)
}
  1. Run go run main.go.
  2. You could also run the following commands:
$ go build
$ ./example

You should see the following output:

$ go run main.go
Listening on port :3333
  1. In a separate Terminal, run the following curl command several times:
$ curl http://localhost:3333

You should see the following output:

$ curl http://localhost:3333
success

$ curl http://localhost:3333
success

$ curl http://localhost:3333
success
  1. In the original main.go, you should see the following:
Listening on port :3333
started request to / with id 100
completed request to / with id 100 in 52.284µs
started request to / with id 101
completed request to / with id 101 in 40.273µs
started request to / with id 102
  1. The go.mod file may be updated and the go.sum file should now be present in the top-level recipe directory.
  2. If you copied or wrote your own tests, go up one directory and run go test. Ensure that all tests pass.