Handle Panic di HTTP Server dengan Middleware Go

Handle Panic di HTTP Server dengan Middleware Go

Kalau kalian pernah coding di Go, kemungkinan besar pernah kena panic. Panic bisa mematikan aplikasi mu kalau tidak di recover. Untungnya di HTTP server bawaan Go sudah ada recovery untuk panic, jadi server tetap bisa berjalan walaupun ada panic. Tapi dari sisi client tidak mendapat response apa-apa kalau server kena panic. Jadi lebih baik kalau kita menggunakan panic recovery kita sendiri yang memberikan response dengan error message ketika ada panic.

Recover panic menggunakan middleware

Untuk recover panic, kita menggunakan fungsi recover. Fungsi recover dipanggil di dalam sebuah defered function. Kalau ada panic, dia akan me-return variable panic yang terjadi. Kalau tidak ada, dia akan return nil. Bisa dilihat contoh di bawah.

1
2
3
4
5
6
7
8
9
func myFunc() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("got panic", err)
        }
    }()

    // do someting that may cause panic..
}

Kita bisa melakukan sesuatu untuk memonitor ketika panic terjadi di middleware, misalkan dengan menulis ke log, mengirim notification ke engineer (menggunakan slack atau email), trigger suatu alert, atau cara lain yang sesuai dengan kebutuhan.

Kenapa kita menggunakan middleware untuk meng-handle panic di HTTP server? Middleware adalah sebuah function yang berjalan sebelum atau sesudah HTTP handler mu berjalan. Jadi ketika ada request ke server, flow nya akan ke middleware dulu, kemudian handler, lalu kembali lagi ke middleware. Jadi sangat cocok untuk menghandle panic di middleware. Ketika panic terjadi, process akan kembali ke middleware, dimana kita menaruh function recover. Middleware juga reusable sehingga kita bisa mengunakannya untuk setiap handler dengan hanya sedikit perubahan di code. Di bawah ini adalah beberapa contoh middleware untuk recover dari panic.

Ada banyak router library yang bisa digunakan di Go. Jadi diberikanlah beberapa contoh middleware untuk beberapa library router.

Go net/http Router

Berikut ini adalah middleware untuk handler net/http bawaan Go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func panicRecovery(h func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                buf := make([]byte, 2048)
                n := runtime.Stack(buf, false)
                buf = buf[:n]

                fmt.Printf("recovering from err %v\n %s", err, buf)
                w.Write([]byte(`{"error":"our server got panic"}`))
            }
        }()

        h(w, r)
    }
}
Middleware ini menangkap panic, menulisnya ke log dan memberikan response error ke client. Cara menggunakan middleware ini:
1
2
3
4
5
6
7
// to use the middleware:
http.HandleFunc("/panic_test", panicRecovery(myHandler))

// the handler to test:
func myHandler(w http.ResponseWriter, r *http.Request) {
    panic("panic dari httpHandler")
}

julienschmidt/httprouter

Ini adalah middleware untuk library julienschmidt/httprouter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func panicRecovery(h func(w http.ResponseWriter, r *http.Request, p httprouter.Params)) func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
    return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
        defer func() {
            if err := recover(); err != nil {
                buf := make([]byte, 2048)
                n := runtime.Stack(buf, false)
                buf = buf[:n]

                fmt.Printf("recovering from err %v\n %s", err, buf)
                w.Write([]byte(`{"error":"our server got panic"}`))
            }
        }()

        h(w, r, p)
    }
}

gorilla/mux

Kalau kamu menggunakan gorilla/mux, kamu bisa menggunakan method Use milik router. Middleware yang didaftarkan menggunakan method Use akan dipanggil untuk semua handler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func panicRecovery(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                buf := make([]byte, 2048)
                n := runtime.Stack(buf, false)
                buf = buf[:n]

                fmt.Printf("recovering from err %v\n %s", err, buf)
                w.Write([]byte(`{"error":"our server got panic"}`))
            }
        }()

        h.ServeHTTP(w, r)
    })
}
Untuk me-register middleware:
1
2
3
4
5
6
    r := mux.NewRouter()

    r.HandleFunc("/panic_test", myHandler)
    r.Use(panicRecovery)

    http.ListenAndServe(":5005", r)

go-chi/chi

chi punya middleware nya sendiri untuk recover dari panic, lihat di sini. Middleware ini me-recover panic dan menampilkan stack trace nya. Tapi kalau ingin menggunakan middleware sendiri, bisa menggunakan middleware yang sama dengan gorilla/mux di atas karena parameter dan return dari function nya sama.

Kesimpulan

Middleware adalah tempat yang cocok untuk meng-handle panic di HTTP server. Walaupun sudah ada middleware bawaan pada HTTP server, akan lebih baik kalau kita menggunakan middleware kita sendiri. Yang terpenting dari menggunakan middleware kita sendiri adalah kita dapat menambahkan monitoring dan alert agar panic dapat segera di atasi. Selain itu kita bisa memberikan response yang cocok dengan structure response server kita. Kita juga dapat menambahkan hal-hal lain yang sesuai dengan kebutuhan, seperti me-response dengan HTTP status code 500 ketika panic terjadi.
Berikan komentar karena comment section selalu terbuka.

go  http 

See also

comments powered by Disqus