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
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>