Routing dengan ServeMux
ServeMux dulu hanya bisa mencocokkan awalan path. Go 1.22 mengubahnya jadi router yang mengerti "GET /produk/{id}", dengan aturan prioritas yang jelas dan tanpa dependensi.
Intisari
- Pola sekarang berbentuk
[METODE ][host]/path:GET /produk/{id}. r.PathValue("id")mengambil parameternya. Wildcard{path...}menangkap sisa jalur.- Pola paling spesifik menang, bukan yang didaftarkan duluan โ konflik yang ambigu membuat panik saat startup.
- Pola berakhiran
/mencocokkan seluruh subtree; tanpa/hanya jalur persis. - Go 1.26: redirect tambah-garis-miring kini memakai 307, bukan 301 โ perilaku lama sempat mengunci cache browser.
Bentuk pola
mux := http.NewServeMux()
mux.HandleFunc("GET /produk", h.Daftar)
mux.HandleFunc("POST /produk", h.Buat)
mux.HandleFunc("GET /produk/{id}", h.Ambil)
mux.HandleFunc("PUT /produk/{id}", h.Ubah)
mux.HandleFunc("DELETE /produk/{id}", h.Hapus)
mux.HandleFunc("GET /produk/{id}/ulasan/{ulasanID}", h.Ulasan)
mux.HandleFunc("GET /berkas/{path...}", h.Berkas) // menangkap sisa jalur
mux.HandleFunc("/", h.TidakDitemukan) // penangkap semua
func (h *Handler) Ambil(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil {
http.Error(w, "id tidak valid", http.StatusBadRequest)
return
}
...
}
PathValue selalu mengembalikan string, dan string apa pun bisa masuk. Setiap
parameter jalur wajib divalidasi โ /produk/abc, /produk/-1, dan
/produk/99999999999999999999 semuanya akan sampai ke handler-mu. Ini titik masuk pertama
data tidak tepercaya, dan Fase 6 kembali ke sini.
Aturan prioritas
Berbeda dari kebanyakan router, urutan pendaftaran tidak berpengaruh. Yang menang adalah pola yang lebih spesifik:
| Permintaan | Pola yang menang | Kalah dari |
|---|---|---|
GET /produk/baru | GET /produk/baru | GET /produk/{id} |
GET /produk/42 | GET /produk/{id} | /produk/ |
POST /produk/42 | POST /produk/{id} | /produk/{id} tanpa metode |
GET /apa/saja | / | โ |
Pola yang benar-benar ambigu membuat program panik saat startup, bukan berperilaku aneh saat
runtime. Contohnya /a/{x}/c versus /a/b/{y}: keduanya cocok untuk
/a/b/c dan tidak ada yang lebih spesifik. Ini keputusan desain yang bagus โ kesalahan
routing ketahuan di detik pertama, bukan di permintaan ke sejuta.
Garis miring di akhir menentukan segalanya
mux.HandleFunc("GET /admin", h.Admin) // HANYA /admin
mux.HandleFunc("GET /admin/", h.Admin) // /admin/ dan SELURUH isinya
| Pola | Cocok | Tidak cocok |
|---|---|---|
/admin | /admin | /admin/pengguna |
/admin/ | /admin/, /admin/pengguna, /admin/a/b/c | โ |
/admin/{$} | hanya /admin/ persis | /admin/pengguna |
Pola {$} ada khusus untuk kasus terakhir: "cocokkan akhir jalur, jangan subtree". Tanpa itu,
pola / akan menangkap semua permintaan yang tidak cocok pola lain โ yang biasanya
memang yang kamu mau untuk halaman 404.
Perilaku redirect yang berubah di Go 1.26. Kalau kamu mendaftarkan /admin/ dan
seseorang membuka /admin, ServeMux mengarahkan ulang ke bentuk bergaris-miring. Sampai
Go 1.25 redirect itu berstatus 301 Moved Permanently โ yang berarti browser dan CDN
menyimpannya selamanya, sehingga mengubah rute nanti jadi menyakitkan. Go 1.26 menggantinya jadi
307 Temporary Redirect.
Menyusun rute untuk aplikasi nyata
func (h *Handler) Rute() http.Handler {
mux := http.NewServeMux()
// Publik
mux.HandleFunc("GET /sehat", h.Sehat)
mux.HandleFunc("GET /produk", h.DaftarProduk)
mux.HandleFunc("GET /produk/{id}", h.AmbilProduk)
// Butuh autentikasi โ subrouter dengan middleware sendiri
priv := http.NewServeMux()
priv.HandleFunc("POST /produk", h.BuatProduk)
priv.HandleFunc("PUT /produk/{id}", h.UbahProduk)
priv.HandleFunc("GET /saya/pesanan", h.PesananSaya)
mux.Handle("/api/", http.StripPrefix("/api",
h.WajibLogin(priv)))
// Berkas statis dari binari
mux.Handle("GET /statis/", http.StripPrefix("/statis",
http.FileServer(http.FS(statisFS))))
// Middleware global, urutan dari luar ke dalam
return Pulihkan(Log(h.log, BatasWaktu(10*time.Second, mux)))
}
http.StripPrefix wajib saat memasang subrouter. Tanpa itu, subrouter melihat jalur
lengkap (/api/produk) sementara polanya ditulis tanpa awalan (/produk), dan
semuanya jadi 404. Ini kesalahan yang selalu terjadi sekali per orang.
Batas ServeMux, dan kapan itu penting
| Kebutuhan | ServeMux | Catatan |
|---|---|---|
| Metode + parameter jalur | โ | Sejak Go 1.22 |
| Wildcard sisa jalur | โ | {path...} |
| Pencocokan host | โ | toko.contoh.com/produk |
| Middleware per grup rute | โ ๏ธ | Bisa, lewat subrouter + StripPrefix โ lebih bertele-tele |
| Batasan regex pada parameter | โ | Validasi di handler |
| 405 Method Not Allowed otomatis | โ | Dengan header Allow yang benar |
| Daftar rute untuk debugging | โ | chi punya Walk; di sini kamu mencatat sendiri |
Untuk aplikasi dengan puluhan rute dan beberapa kelompok middleware, chi (Fase 7) memberi
sintaks grup yang jauh lebih ringkas โ dengan tipe yang sama persis, sehingga handler-mu tidak berubah
sebaris pun. Untuk aplikasi kecil sampai menengah, ServeMux benar-benar cukup.
Latihan: daftarkan GET /produk/{id} dan GET /produk/baru sekaligus,
lalu panggil keduanya dan buktikan yang spesifik menang meski didaftarkan belakangan. Lalu daftarkan
/a/{x}/c dan /a/b/{y} bersamaan dan jalankan โ baca pesan panik startup-nya.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.