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

Timeout & batas server

Default net/http adalah "tidak ada batas": tidak ada batas waktu membaca, menulis, maupun menganggur. Untuk contoh di tutorial itu praktis; untuk server yang terbuka ke internet itu kerentanan.

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

Intisari

  • ReadHeaderTimeout adalah pertahanan terhadap Slowloris โ€” ribuan koneksi yang mengirim header satu byte per menit.
  • WriteTimeout dihitung sejak header permintaan selesai dibaca, jadi ia mencakup seluruh waktu handler-mu.
  • IdleTimeout mengatur berapa lama koneksi keep-alive dipertahankan; tanpa itu ia mewarisi ReadTimeout.
  • Timeout server adalah jaring pengaman; kendali sebenarnya ada di context per permintaan.
  • Unggahan berkas besar dan SSE butuh pengecualian โ€” pakai http.ResponseController, bukan menaikkan timeout global.

Konfigurasi yang layak produksi

srv := &http.Server{
	Addr:    ":8080",
	Handler: handler,

	// Waktu maksimum membaca HEADER. Pertahanan Slowloris.
	ReadHeaderTimeout: 5 * time.Second,

	// Header + body. Naikkan hanya kalau memang menerima unggahan besar.
	ReadTimeout: 15 * time.Second,

	// Dari header selesai dibaca sampai respons selesai ditulis.
	// Ini mencakup SELURUH waktu kerja handler-mu.
	WriteTimeout: 15 * time.Second,

	// Berapa lama koneksi keep-alive menganggur sebelum ditutup.
	// Harus LEBIH BESAR dari idle timeout ALB (Fase 10).
	IdleTimeout: 65 * time.Second,

	// Batas ukuran seluruh header. Default 1 MB.
	MaxHeaderBytes: 1 << 20,
}
FieldDihitung dariSampaiDefault
ReadHeaderTimeoutKoneksi diterimaHeader selesai dibacatak terbatas
ReadTimeoutKoneksi diterimaBody selesai dibacatak terbatas
WriteTimeoutHeader selesai dibacaRespons selesai ditulistak terbatas
IdleTimeoutRespons selesaiPermintaan berikutnyaIkut ReadTimeout

Serangan yang dicegahnya

Slowloris: buka 10.000 koneksi, kirim
    "GET / HTTP/1.1\r\n"
lalu satu header tiap 30 detik, tanpa pernah selesai.

Tanpa ReadHeaderTimeout:
  โ€ข 10.000 goroutine hidup, masing-masing menunggu selamanya
  โ€ข 10.000 file descriptor terpakai
  โ€ข memori naik, pengguna sungguhan tidak kebagian koneksi
  โ€ข satu laptop cukup untuk melakukannya

ReadHeaderTimeout adalah satu baris dengan rasio manfaat tertinggi di seluruh konfigurasi server Go. Linter gosec (aturan G112) menandai server tanpa field ini sebagai temuan keamanan โ€” pasang golangci-lint di Fase 8 dan ia akan mengingatkanmu.

Kenapa WriteTimeout sering menipu

// Handler ini butuh 20 detik. Dengan WriteTimeout 15 detik:
// koneksi diputus di detik 15, dan KLIEN menerima respons terpotong โ€”
// sementara handler-mu TETAP BERJALAN sampai selesai.
func lambat(w http.ResponseWriter, r *http.Request) {
	hasil := prosesBerat(r.Context())   // 20 detik
	json.NewEncoder(w).Encode(hasil)     // koneksi sudah mati
}

Timeout server memutus koneksi; ia tidak menghentikan handler. Yang menghentikan handler adalah context. Karena itu keduanya dipakai bersama:

// Middleware timeout memasang tenggat DI CONTEXT โ€” inilah yang benar-benar
// menghentikan pekerjaan, termasuk kueri database di lapisan terdalam.
func BatasWaktu(d time.Duration) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx, batal := context.WithTimeout(r.Context(), d)
			defer batal()
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}
Timeout serverTimeout context
Yang dihentikanKoneksi TCPPekerjaan di dalam handler
Berlaku untukSemua permintaan, seragamBisa berbeda per rute
Handler tahu?TidakYa โ€” lewat ctx.Done()
PerannyaJaring pengaman terakhirKendali sebenarnya

Endpoint yang perlu pengecualian

// Unggahan besar atau SSE: perpanjang tenggat per permintaan,
// tanpa melonggarkan seluruh server.
func unggah(w http.ResponseWriter, r *http.Request) {
	rc := http.NewResponseController(w)          // Go 1.20+
	_ = rc.SetReadDeadline(time.Now().Add(10 * time.Minute))
	_ = rc.SetWriteDeadline(time.Time{})          // tanpa batas tulis

	...
}

// Server-sent events: matikan batas tulis dan flush setiap pesan.
func peristiwa(w http.ResponseWriter, r *http.Request) {
	rc := http.NewResponseController(w)
	_ = rc.SetWriteDeadline(time.Time{})

	w.Header().Set("Content-Type", "text/event-stream")
	for {
		select {
		case <-r.Context().Done():
			return
		case p := <-pesan:
			fmt.Fprintf(w, "data: %s\n\n", p)
			_ = rc.Flush()
		}
	}
}

http.ResponseController menggantikan trik lama berupa type assertion ke http.Flusher dan menyetel WriteTimeout: 0 untuk seluruh server. Sebelumnya, satu endpoint streaming memaksa seluruh server berjalan tanpa batas tulis โ€” kompromi yang tidak perlu lagi diambil.

Menyelaraskan dengan ALB

SetelanNilaiKenapa
ALB idle timeout60 dtkDefault AWS
Go IdleTimeout65 dtkHarus lebih besar, kalau tidak akan ada 502 sesekali
ALB deregistration delay30 dtkHarus โ‰ฅ waktu graceful shutdown

502 acak yang sulit dilacak hampir selalu ini penyebabnya. Kalau server Go menutup koneksi keep-alive lebih dulu daripada ALB menyadarinya, ALB bisa mengirim permintaan ke koneksi yang sedang ditutup โ€” dan hasilnya 502 untuk pengguna sungguhan, dengan log aplikasi yang bersih total. Aturannya satu kalimat: IdleTimeout aplikasi harus lebih besar daripada idle timeout load balancer.

Latihan: jalankan server tanpa ReadHeaderTimeout, lalu buka koneksi dengan telnet localhost 8080, ketik GET / HTTP/1.1 dan tekan Enter sekali โ€” lalu diamkan. Cek runtime.NumGoroutine() lewat endpoint pprof dan lihat goroutine itu bertahan. Pasang ReadHeaderTimeout: 5 * time.Second dan ulangi.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.