DEV Community

Cover image for Golang http/net middleware
Adiatma Kamarudin
Adiatma Kamarudin

Posted on

Golang http/net middleware

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

touch main.go
Enter fullscreen mode Exit fullscreen mode

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

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)
}
Enter fullscreen mode Exit fullscreen mode

...execute main.go with go run

go run main.go
Enter fullscreen mode Exit fullscreen mode

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

curl http://localhost:8000
{"message": "hello world.."}
Enter fullscreen mode Exit fullscreen mode

Step 2 - Add middleware

Open main.go then write function middleware.

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)
    })
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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

curl http://localhost:8000
Enter fullscreen mode Exit fullscreen mode
❯ go run main.go
GET / --> log from middleware
Enter fullscreen mode Exit fullscreen mode

Ok, next we need to add context in middleware.

Step - 4 Add context in middleware

// create context
ctx := context.WithValue(r.Context(), "data", "data:test")
Enter fullscreen mode Exit fullscreen mode

Please learn more about the context in here

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

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))
    })
}
Enter fullscreen mode Exit fullscreen mode

Step - 6 Get context data.

 getContextData := r.Context().Value("data")
Enter fullscreen mode Exit fullscreen mode

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

...
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)
}
Enter fullscreen mode Exit fullscreen mode
❯ go run main.go
GET / -> log from middleware
data:test -> context from middleware
Enter fullscreen mode Exit fullscreen mode

...full code

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)
}
Enter fullscreen mode Exit fullscreen mode

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:

Discussion (0)