Golang http/net middleware
Tags: go, http, middleware
Published: 2021-08-26 02:05:46

> middleware provide a mechanism for filtering or inspecting http requests entering to your application, example you need to verify user authentication or authorization before access something request to your http, middleware running before and and after request happened.

Today we can try the middleware with package http/net.

## Step 1 - Create simple http server

```bash
touch main.go
```

...next open `main.go` with your code editor.

```go
package main

import (
  "http/net"
  "io"
)

func main() {
   r := http.NewServeMux()

   r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
       io.WriteString(rw, `{"message": "hello world.."}`)
   })

   http.ListenAndServe(":8000", r)
}
```

...execute `main.go` with `go run`

```bash
go run main.go
```

...open another terminal then run `curl http://localhost:8000`

```bash
curl http://localhost:8000
{"message": "hello world.."}
```

## Step 2 - Add middleware

Open `main.go` then write function middleware.

```go
func TheLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		fmt.Println(fmt.Sprintf("%s %s", r.Method, r.URL.Path))
		next.ServeHTTP(rw, r)
	})
}
```

Interface `http.Handler` is a handler response to an HTTP request. `http.Handler` have return `ServeHTTP` to reply headers and data to the ResponseWriter.

I name middleware with `TheLogger` to get response method, and url path.

Next, in main function we just wrap the router with `TheLogger` middleware, cause `http.NewServeMux` also implement interface `http.Router`, and for more please check in next steps.

## Step 3 - Use `TheLogger` in http server.

```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func TheLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		fmt.Println(fmt.Sprintf("%s %s", r.Method, r.URL.Path))
		next.ServeHTTP(rw, r)
	})
}

func main() {
	r := http.NewServeMux()

	r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		io.WriteString(rw, `{"message": "hello world.."}`)
	})

	m := TheLogger(r)
	http.ListenAndServe(":8000", m)
}
```

...save and run with `go run main.go`

```bash
curl http://localhost:8000
```

```
❯ go run main.go
GET / --> log from middleware
```

Ok, next we need to add context in middleware.

## Step - 4 Add context in middleware

```go
// create context
ctx := context.WithValue(r.Context(), "data", "data:test")
```

Please learn more about the context in [here](https://pkg.go.dev/context#Background)

## Step - 5 Add `http.Request` context to `TheLogger` middleware.

```go
func TheLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		fmt.Println(fmt.Sprintf("%s %s", r.Method, r.URL.Path))
		// create context
		ctx := context.WithValue(r.Context(), "data", "data:test")
		// inject the context in http.Request with r.WithContext
		next.ServeHTTP(rw, r.WithContext(ctx))
	})
}
```

## Step - 6 Get context data.

```go
 getContextData := r.Context().Value("data")
```

`r http.Request` get context with `r.Context().Value(<context_key>)`

```go
...
func main() {
	r := http.NewServeMux()

	r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		// get context data
		getContextData := r.Context().Value("data")
		fmt.Println(getContextData)
		io.WriteString(rw, `{"message": "hello world.."}`)
	})

	m := TheLogger(r)

	http.ListenAndServe(":8000", m)
}
```

```bash
❯ go run main.go
GET / -> log from middleware
data:test -> context from middleware
```

...full code

```go
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
)

func TheLogger(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		// print log r.Method and r.URL.Path
		fmt.Println(fmt.Sprintf("%s %s", r.Method, r.URL.Path))
		// create context
		ctx := context.WithValue(r.Context(), "data", "data:test")
		// inject the context in http.Request with r.WithContext
		next.ServeHTTP(rw, r.WithContext(ctx))
	})
}

func main() {
	r := http.NewServeMux()

	r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		// get context data
		getContextData := r.Context().Value("data")
		fmt.Println(getContextData)
		io.WriteString(rw, `{"message": "hello world.."}`)
	})

	m := TheLogger(r)

	http.ListenAndServe(":8000", m)
}
```

Final, In real case you can using middleware to validate authorization user and etc, and request with context for sharing data via `http.Request`.

Reference:
- https://pkg.go.dev/context#pkg-overview
- https://pkg.go.dev/net/http