Belajar Chi - Context, Timeout & Concurrency
Series/Belajar Chi/Episode 12
Episode 12 of 23

Belajar Chi - Context, Timeout & Concurrency

Episode ini membedah request lifecycle: nilai request-scoped lewat context, pembatalan request, dan middleware.Timeout untuk membatasi durasi. Kalian juga belajar memakai goroutine di handler, middleware.Throttle untuk membatasi konkurensi, serta sinkronisasi akses terhadap state bersama.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Go adalah bahasa yang dibangun untuk konkurensi, dan net/http menjalankan setiap request dalam goroutine sendiri. Episode 12 mengajarkan cara memakai kekuatan itu dengan aman: membawa data antar lapisan lewat context, membatasi waktu eksekusi, dan mengatur beban konkurensi.

Tanpa pemahaman ini, handler bisa menghabiskan resource tanpa batas, state bersama bisa diakses bersamaan secara tidak aman, dan request yang sudah dibatalkan klien tetap memakan kerja server.

Request-Scoped Values via Context

Menyimpan Nilai di Context

Context cocok untuk data yang melekat pada satu request — user ID, request ID, role:

Nilai request-scoped
type userKey struct{}
 
ctx := context.WithValue(req.Context(), userKey{}, user)
next.ServeHTTP(w, req.WithContext(ctx))

context.WithValue(req.Context(), userKey{}, user) menempelkan nilai ke context, lalu req.WithContext(ctx) membawa context baru bersama request. Tipe key userKey (struct kosong) mencegah tabrakan dengan key package lain.

Membaca Nilai di Handler

Membaca nilai dari context
func currentUser(req *http.Request) (User, bool) {
    u, ok := req.Context().Value(userKey{}).(User)
    return u, ok
}

req.Context().Value(userKey{}) mengambil nilai yang disimpan. Type assertion memastikan nilai bertipe User — jika tidak ada atau salah tipe, dikembalikan false.

Pembatalan Request

Context Terbatalkan

Context request dibatalkan otomatis saat klien memutus koneksi. Semua operasi yang menerima context — database, HTTP client, channel select — ikut berhenti:

Hormati pembatalan context
func handleSlow(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
 
    select {
    case result := <-compute(ctx):
        writeJSON(w, http.StatusOK, result)
    case <-ctx.Done():
        return
    }
}

<-ctx.Done() memberi tahu goroutine bahwa request sudah tidak relevan. Handler langsung kembali tanpa menulis respons — menghemat kerja yang sia-sia.

middleware.Timeout

Batas Waktu per Request

Jangan biarkan handler berjalan selamanya:

Timeout bawaan chi
r.Use(middleware.Timeout(5 * time.Second))

middleware.Timeout(5 * time.Second) membungkus handler dengan context yang dibatalkan setelah 5 detik. Handler yang melampaui batas akan berhenti pada checkpoint ctx.Done() berikutnya, dan respons default dikirim.

Kombinasi dengan Context

Timeout middleware berfungsi karena menginjeksi context berbatas waktu ke request. Itulah mengapa handler wajib meneruskan req.Context() ke semua operasi — jika context diabaikan, timeout tidak akan pernah dipicu.

Goroutine di Handler

Jalankan Task Paralel

Beban berat bisa dipecah ke goroutine untuk mempercepat respons:

Goroutine dalam handler
r.Get("/dashboard", func(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
 
    userCh := make(chan User)
    statCh := make(chan Stats)
 
    go func() {
        userCh <- userRepo.FindByID(ctx, userID)
    }()
    go func() {
        statCh <- statsRepo.Load(ctx)
    }()
 
    user := <-userCh
    stats := <-statCh
 
    writeJSON(w, http.StatusOK, map[string]any{
        "user":  user,
        "stats": stats,
    })
})

Kedua query berjalan paralel di goroutine, sehingga total waktu hanya sebesar query paling lambat, bukan penjumlahan keduanya. <-userCh menunggu hasil dari masing-masing channel.

Aturan Emas Goroutine

  • Selalu teruskan context ke goroutine agar bisa dibatalkan.
  • Jangan menulis ke http.ResponseWriter dari goroutine lain.
  • Kelola goroutine dengan channel atau sync.WaitGroup.
  • Pastikan setiap goroutine punya jalan keluar — hindari kebocoran.

Throttle dan State Bersama

Batasi Konkurensi

middleware.Throttle membatasi jumlah request bersamaan:

Batasi konkurensi
r.Use(middleware.Throttle(50))

middleware.Throttle(50) menolak request yang melebihi 50 request berjalan bersamaan — melindungi server dari lonjakan beban. Untuk antrian dengan buffer, gunakan middleware.ThrottleBacklog.

Sinkronisasi State Bersama

Saat beberapa goroutine memakai data yang sama, lindungi dengan mutex:

State bersama dengan mutex
type Counter struct {
    mu     sync.Mutex
    visits int
}
 
func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.visits++
}

c.mu.Lock() memastikan hanya satu goroutine yang mengubah visits pada satu waktu. Tanpa mutex, dua request bersamaan bisa saling menimpa nilai.

Penutup

Inti yang harus dibawa pulang:

  • Context membawa nilai request-scoped; pakai struct key untuk menghindari tabrakan.
  • Hormati ctx.Done() untuk pembatalan request dari klien.
  • middleware.Timeout membatasi durasi eksekusi per request.
  • Goroutine mempercepat handler paralel; selalu teruskan context.
  • middleware.Throttle membatasi konkurensi maksimal.
  • State bersama wajib dilindungi sync.Mutex.

Di episode 13 selanjutnya kita mengamankan akses: autentikasi dan otorisasi — JWT dengan golang-jwt sebagai middleware, BasicAuth bawaan, session cookie dan refresh token, RBAC middleware, serta integrasi OAuth2 dan OpenID Connect dengan Keycloak.

Belajar Chi - Context, Timeout & Concurrency | Belajar Chi