Paket net/http juga menyediakan sisi server, yaitu bagian program yang menerima permintaan dan menuliskan respons. Topik ini membahas tiga lapisan yang membentuk server HTTP Go, yaitu fungsi handler yang menulis respons, ServeMux yang menyalurkan permintaan ke handler berdasarkan pola rute, dan http.Server yang menerima koneksi sampai dimatikan secara teratur melalui graceful shutdown. Handler pada contoh diuji dengan perekam respons dari paket httptest, sehingga perilakunya dapat dieksekusi tanpa membuka port. Setelah membacanya, server dapat dibangun dari handler kecil yang mudah diuji dan dimatikan tanpa memotong permintaan yang sedang berjalan.
Handler dan penguji perekam#
Handler HTTP berupa fungsi dengan dua parameter, yaitu http.ResponseWriter untuk menulis respons dan *http.Request untuk membaca permintaannya. Paket httptest menyediakan httptest.NewRequest untuk membentuk permintaan buatan dan httptest.NewRecorder untuk perekam yang menyimpan kode status serta tubuh respons. Tanda tangan itu membuat handler dapat dipanggil langsung tanpa jaringan, cukup dengan memberi perekam sebagai penulisnya. Setelah handler dieksekusi terhadap perekam, seluruh hasilnya dapat diperiksa dari field Code dan Body. Program berikut mendefinisikan handler salam dan mengujinya dengan cara itu.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func salam(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Halo, %s!", r.URL.Query().Get("nama"))
}
func main() {
req := httptest.NewRequest("GET", "/salam?nama=Ali", nil)
rec := httptest.NewRecorder()
salam(rec, req)
fmt.Println("kode:", rec.Code)
fmt.Println(rec.Body.String())
}kode: 200
Halo, Ali!Handler salam membaca parameter kueri nama dan menuliskannya kembali, sedangkan pemanggilan langsung pada perekam menangkap hasilnya tanpa satu koneksi pun dibuka. Apabila handler tidak memanggil WriteHeader, responsnya otomatis berkode 200, dan pemanggilan WriteHeader yang eksplisit diperlukan hanya ketika status lain dikirim. Kode status 200 pada keluaran berasal dari perilaku default itu, sehingga handler yang paling sederhana pun dapat diverifikasi dengan perekam yang sama.
Tipe http.HandlerFunc menjadi jembatannya, karena fungsi biasa dikonversi menjadi http.Handler yang menuntut satu metode ServeHTTP. Handler tunggal dan mux memenuhi kontrak yang sama, sehingga keduanya dapat dipasang langsung pada field Handler milik http.Server.
Pola rute pada ServeMux#
Tipe http.ServeMux menyalurkan permintaan ke handler berdasarkan pola rute yang didaftarkan melalui HandleFunc. Sejak Go 1.22, pola tersebut dapat mencantumkan metode HTTP dan nama segmen, misalnya GET /salam/{nama} yang hanya melayani metode GET dan menangkap segmen terakhir sebagai parameter bernama nama. Nilai parameternya dibaca di dalam handler melalui r.PathValue. Permintaan dengan metode yang tidak cocok ditolak dengan status 405, sedangkan rute yang tidak terdaftar jatuh ke status 404. Program berikut memasang dua rute, kemudian menguji tiga permintaan berbeda melalui mux yang sama.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
func salam(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Halo, %s!", r.PathValue("nama"))
}
func status(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
fmt.Fprint(w, "status khusus")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /salam/{nama}", salam)
mux.HandleFunc("/status", status)
uji := func(metode, target string) {
req := httptest.NewRequest(metode, target, nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
fmt.Printf("%s %s -> %d %s\n", metode, target, rec.Code, rec.Body.String())
}
uji("GET", "/salam/Ali")
uji("GET", "/status")
uji("GET", "/tidak-ada")
}GET /salam/Ali -> 200 Halo, Ali!
GET /status -> 418 status khusus
GET /tidak-ada -> 404 404 page not foundTiga baris keluaran memperlihatkan tiga jalur penyaluran. Permintaan pertama masuk ke rute berparameter dan PathValue("nama") mengembalikan Ali. Permintaan kedua masuk ke rute status, yang memanggil WriteHeader(http.StatusTeapot) sehingga keluarannya berkode 418 dan membuktikan bahwa status khusus dapat dikirim dari handler mana pun. Permintaan ketiga tidak cocok dengan rute mana pun, sehingga mux menulis status 404 dengan teks bawaannya tanpa menyentuh handler yang dibuat sendiri.
Server utuh dan graceful shutdown#
Dengan handler dan mux yang siap, server utuh tinggal membungkusnya dalam http.Server beserta alamatnya, kemudian menjalankan ListenAndServe yang berjalan terus hingga dipanggil Shutdown. Program server semacam ini tidak pernah berhenti dengan sendirinya, sehingga contoh lengkapnya tidak dijalankan pada topik ini dan hanya berupa fragmen berikut.
// tidak-dijalankan
srv := &http.Server{Addr: ":8080", Handler: mux}
ctxBerhenti, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Println("error:", err)
}
}()
<-ctxBerhenti.Done()
ctxMatikan, batal := context.WithTimeout(context.Background(), 5*time.Second)
defer batal()
srv.Shutdown(ctxMatikan)Fragmen di atas bekerja dalam tiga tahap. ListenAndServe menerima koneksi secara terus-menerus dan dijalankan pada goroutine terpisah, sementara program utama menunggu sinyal interupsi dari terminal melalui signal.NotifyContext. Ketika sinyal diterima, Shutdown berhenti menerima permintaan baru, menyelesaikan permintaan yang sedang berjalan, dan dibatasi oleh context.WithTimeout selama lima detik agar tidak menunggu selamanya. Pola inilah yang dimaksud graceful shutdown, yaitu penghentian server tanpa memotong permintaan yang belum selesai. Tenggat lima detik pada contohnya bersifat penyesuaian, karena permintaan yang berjalan lama membutuhkan tenggat yang lebih besar. Tanpa tenggat itu, Shutdown menunggu tanpa batas hingga seluruh permintaan selesai dengan sendirinya.
Kedua program yang dapat dieksekusi dijalankan dari terminal sebagaimana berikut.
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gopustaka/server-http
cp uji-handler.go rute-mux.go /tmp/gopustaka/server-http/
cd /tmp/gopustaka/server-http
# jalankan penguji handler salam
go run uji-handler.go
# jalankan penguji pola rute mux
go run rute-mux.gokode: 200
Halo, Ali!
GET /salam/Ali -> 200 Halo, Ali!
GET /status -> 418 status khusus
GET /tidak-ada -> 404 404 page not foundKesalahan umum#
- Menulis tubuh respons sebelum menetapkan status. Status dikirim bersama tulisan pertama, sehingga
WriteHeader(500)setelahnya tidak berpengaruh dan client tetap menerima 200. Perbaikannya, panggilWriteHeaderlebih dahulu ketika statusnya bukan 200. - Menganggap rute
GET /salam/{nama}melayani metode lain. Pola yang mencantumkan metode menolak metode lain dengan status 405, sehingga permintaan POST ke rute itu tidak pernah sampai ke handlernya. Perbaikannya, daftarkan pola metode lain secara eksplisit bila memang diperlukan. - Memanggil
ListenAndServetanpa rencana pematian. Program berhenti seketika ketika interupsi diterima, dan permintaan yang sedang berjalan terpotong di tengah. Perbaikannya, bungkus server dalamhttp.Serverdan panggilShutdownsaat sinyal berhenti diterima. - Menyimpan keadaan pada variabel global tanpa perlindungan. Setiap permintaan dapat ditangani goroutine yang berbeda, sehingga variabel bersama yang ditulis tanpa sinkronisasi memicu kebalapan data. Perbaikannya, lindungi keadaan tersebut dengan mutex atau kumpulkan dalam satu struct yang dikelola secara benar.
- Menjalankan
ListenAndServepada goroutine tanpa mencatat errornya. Error pada penerimaan koneksi dapat berlalu tanpa terlihat, sementara program terus berjalan tanpa server yang hidup. Perbaikannya, catat error tersebut kecualihttp.ErrServerClosedyang menandakan pemadaman yang disengaja.
Ringkasan#
- Handler HTTP berupa fungsi dengan
http.ResponseWriterdan*http.Request, sehingga dapat diuji langsung denganhttptest.NewRecordertanpa jaringan. ServeMuxmenyalurkan permintaan berdasarkan pola rute yang memuat metode dan segmen berparameter, denganPathValuesebagai pembacanya.- Rute yang tidak terdaftar menghasilkan 404, sedangkan metode yang tidak cocok menghasilkan 405, keduanya dari mux tanpa kode tambahan.
http.ServerdenganListenAndServedanShutdownbeserta batas waktu memungkinkan graceful shutdown yang menyelesaikan permintaan berjalan.
Referensi#
- Package net/http — The Go Authors — Dokumentasi resmi Handler, ServeMux, Server, dan semantik pemadamannya.
- Package net/http/httptest — The Go Authors — Dokumentasi resmi NewRequest dan NewRecorder untuk pengujian handler.
- Routing Enhancements for Go 1.22 — The Go Blog — Artikel resmi tentang pola rute metode dan segmen berparameter pada ServeMux.