programming

19 How to Understanding HTTP Middleware in Golang

Middleware

In web creation, we often hear the concept of middleware or filter or interceptor which is a feature that we can add code to before and after a handler is executed.

sequenceDiagram
    actor Client
    participant Server
		participant Middleware
		participant Handler
    Client->>Server: 1 Event
    Server->>Middleware: 2 Dispatch
		Middleware->>Handler: 3 Forward

		Handler->>Middleware: 4 Return
		Middleware->>Server: 5 Return
		Server->>Client: 6 Response

Unfortunately, in Golang Web there is no middleware available, but because the handler structure uses a good interface, we can create our own middleware using the handler.

Example of Middleware Implementation

OK, we will try to create a log middleware in the project that we created previously. Create the log_middleware.go file first then fill the file with the code below.

type LogMiddleware struct {
	Handler http.Handler
}

func (middleware *LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Before Execute Handler")
	middleware.Handler.ServeHTTP(w, r)
	fmt.Println("After Execute Handler")
}

Then initialize the middleware before the HTTP server is run in the main.go file as below.

...
...
logMiddleware := new(LogMiddleware)
logMiddleware.Handler = mux

server := http.Server{
	Addr:    "localhost:8080",
	Handler: logMiddleware,
}

After that, do build and rerun the program that has added middleware. So when the program accesses a page, it should print a log on the terminal like the one below.

➜  learn-golang-web git:(main) ✗ go build && ./learn-golang-web
Before Execute Handler
After Execute Handler
Before Execute Handler
After Execute Handler

Error Handler

We can also use middleware to handle errors so that if a panic occurs in the handler we can recover in the middleware and change the panic into an error response. So, with this we can ensure that our application does not stop and continues to run as before.

Suppose we create a panic handler function as below

mux.HandleFunc("/panic", func(w http.ResponseWriter, r *http.Request) {
	panic("upps")
})

And the program will stop and there will be a ‘panic’ in our program.

➜  learn-golang-web git:(main) ✗ go build && ./learn-golang-web
Before Execute Handler
2023/09/30 15:28:34 http: panic serving 127.0.0.1:55818: upps
goroutine 38 [running]:
net/http.(*conn).serve.func1(0x140001cc000)
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:1802 +0xdc
panic({0x10265bee0, 0x1026b5768})
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/runtime/panic.go:1052 +0x2ac
main.main.func4({0x1026be490, 0x14000170000}, 0x14000156100)
        /Users/ihsanarif/Documents/ihsan/tutorial/learn-golang-web/main.go:66 +0x38
net/http.HandlerFunc.ServeHTTP(0x1026b4760, {0x1026be490, 0x14000170000}, 0x14000156100)
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:2047 +0x40
net/http.(*ServeMux).ServeHTTP(0x14000194300, {0x1026be490, 0x14000170000}, 0x14000156100)
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:2425 +0x18c
main.(*LogMiddleware).ServeHTTP(0x1400018e180, {0x1026be490, 0x14000170000}, 0x14000156100)
        /Users/ihsanarif/Documents/ihsan/tutorial/learn-golang-web/log_middleware.go:14 +0x9c
net/http.serverHandler.ServeHTTP({0x140001c8000}, {0x1026be490, 0x14000170000}, 0x14000156100)
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:2879 +0x444
net/http.(*conn).serve(0x140001cc000, {0x1026bf6a0, 0x1400018a960})
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:1930 +0xb6c
created by net/http.(*Server).Serve
        /opt/homebrew/Cellar/go/1.17.5/libexec/src/net/http/server.go:3034 +0x4b8

How will you be able to handle panic handlers and error handlers using Middleware? Below we will try to create an error handler middleware. First, we create the file error_middleware.go then fill in the file with the following.

func (middleware *ErrorMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		err := recover()
		fmt.Println("recover :", err)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error: %v", err)
		}
	}()
	middleware.Handler.ServeHTTP(w, r)
}

Then update the middleware that we added to the main.go file to look like the one below.

logMiddleware := new(LogMiddleware)
logMiddleware.Handler = mux

errMiddleware := &ErrorMiddleware{
	Handler: logMiddleware,
}

server := http.Server{
	Addr:    "localhost:8080",
	Handler: errMiddleware,
}

Finally, we build and rerun the program then try to access the browser page with the URL

http://localhost:8080/panic

Then it will appear on the accessed page as below

tutorial golang web panic error middleware

And in our program there will be no ‘panic’ that causes our program to stop.

➜  learn-golang-web git:(main) ✗ go build && ./learn-golang-web
Before Execute Handler
recover : upps
Before Execute Handler
After Execute Handler
recover : <nil>
comments powered by Disqus