โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 3 ยท HTTP โ€” Request sampai Response

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.

Sumber asli pkg.go.dev Resmi Rangkuman ~7 menit baca

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:

PermintaanPola yang menangKalah dari
GET /produk/baruGET /produk/baruGET /produk/{id}
GET /produk/42GET /produk/{id}/produk/
POST /produk/42POST /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
PolaCocokTidak 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

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