Go terkenal karena mampu menyajikan layanan web dari satu binary tanpa framework tambahan. Fondasinya ada di package net/http: sebuah fungsi bernama handler menerima request, menulis response, dan http.ServeMux membagikan request ke handler yang tepat. Pada chapter ini kita akan membangun handler pertama, mendaftarkannya ke ServeMux, menulis header, status code, dan body dengan urutan yang benar, lalu melihat bentuk lengkap http.Server untuk produksi. Sesuai kontrak section ini, contoh program diuji lewat httptest tanpa membuka port sungguhan.

Handler: Fungsi Dua Parameter #

Seluruh logika server Go bermuara pada satu signature:

go
func(w http.ResponseWriter, r *http.Request)

Parameter w bertipe http.ResponseWriter adalah tujuan penulisan response, sedangkan r bertipe *http.Request berisi seluruh data permintaan yang masuk. Signature tersebut adalah bentuk konkret dari interface http.Handler, yang hanya memiliki satu method:

go
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

Fungsi biasa diubah menjadi http.Handler melalui tipe adapter http.HandlerFunc. Mekanisme konversi inilah yang membuat fungsi sederhana bisa langsung dipakai sebagai handler.

Mendaftarkan Handler dengan ServeMux #

http.ServeMux adalah router bawaan standard library. Kode berikut membuat satu mux lalu mendaftarkan fungsi haloHandler untuk path /halo:

go
mux := http.NewServeMux()
mux.HandleFunc("/halo", haloHandler)

Pemanggilan mux.HandleFunc menerima path dan fungsi ber-signature handler, lalu menyimpan keduanya di dalam mux. Ketika request masuk, mux memilih handler yang paling cocok lalu memanggil handler tersebut. Detail pola path seperti "GET /items/{id}" akan kita bedah tuntas pada chapter Routing .

Penulisan response memiliki urutan yang ketat: atur header lebih dulu, lalu status code, lalu body. Ketiga baris ini adalah pola standarnya:

go
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(items)

Pemanggilan w.Header().Set harus terjadi sebelum WriteHeader dan Write, karena header tidak bisa diubah setelah response mulai terkirim. Pemanggilan w.WriteHeader hanya boleh sekali; pemanggilan w.Write tanpa WriteHeader sebelumnya secara otomatis mengirim status 200 OK.

Dari ServeMux ke http.Server #

Untuk membuka layanan sungguhan, mux dilempar ke http.ListenAndServe:

go
log.Fatal(http.ListenAndServe(":8080", mux))

Bentuk singkat di atas memadai untuk pengembangan, tetapi produksi membutuhkan struct http.Server agar timeout bisa diatur eksplisit:

go
srv := &http.Server{
	Addr:         ":8080",
	ReadTimeout:  5 * time.Second,
	WriteTimeout: 10 * time.Second,
	IdleTimeout:  120 * time.Second,
	Handler:      mux,
}
log.Fatal(srv.ListenAndServe())

Field ReadTimeout membatasi waktu membaca request, WriteTimeout membatasi waktu menulis response, dan IdleTimeout mengatur umur koneksi nganggur. Penghentian server secara halus — menunggu request aktif selesai sebelum proses keluar — dibahas khusus pada chapter Graceful Shutdown .

Program Utuh: Server Tanpa Port #

Berkas server-demo.go pada bagian bawah halaman membangun dua handler — satu fungsi bernama, satu fungsi anonim — lalu menguji keduanya lewat request tiruan.

go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

type Item struct {
	ID   int    `json:"id"`
	Nama string `json:"nama"`
}

// haloHandler memiliki signature handler: dua parameter w dan r.
func haloHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprintln(w, "Halo dari handler")
}

func main() {
	mux := http.NewServeMux()

	// Fungsi biasa otomatis menjadi handler lewat mux.HandleFunc.
	mux.HandleFunc("/halo", haloHandler)

	// Handler anonim: atur header, status code, lalu tulis body JSON.
	mux.HandleFunc("/items", func(w http.ResponseWriter, r *http.Request) {
		items := []Item{{ID: 1, Nama: "Buku Go"}, {ID: 2, Nama: "Mouse"}}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(items)
	})

	// Uji handler tanpa membuka port: request tiruan + perekam response.
	uji(mux, http.MethodGet, "/halo")
	uji(mux, http.MethodGet, "/items")
}

func uji(h http.Handler, method, path string) {
	req := httptest.NewRequest(method, path, nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	fmt.Printf("%s %s -> %d (%s)\n", method, path, rec.Code, rec.Header().Get("Content-Type"))
	fmt.Printf("  body: %s", rec.Body.String())
}

Jalankan berkas tersebut dengan perintah berikut dari direktori chapter:

bash
go run server-demo.go

Output yang Dihasilkan:

text
GET /halo -> 200 (text/plain; charset=utf-8)
  body: Halo dari handler
GET /items -> 200 (application/json)
  body: [{"id":1,"nama":"Buku Go"},{"id":2,"nama":"Mouse"}]

Mari kita telaah baris demi baris program di atas:

  1. Baris 10-13 (type Item struct ...): mendefinisikan struct data dengan tag JSON yang menentukan bentuk body response.
  2. Baris 16-19 (func haloHandler(...)): handler bernama dengan signature dua parameter w dan r; handler mengatur Content-Type lalu menulis satu baris teks.
  3. Baris 22 (mux := http.NewServeMux()): membuat router kosong yang siap menerima pendaftaran handler.
  4. Baris 25 (mux.HandleFunc("/halo", haloHandler)): mendaftarkan haloHandler untuk path /halo; konversi fungsi ke http.HandlerFunc terjadi otomatis.
  5. Baris 28-33 (mux.HandleFunc("/items", ...)): handler anonim yang mengatur header, status 200, lalu men-encode slice Item menjadi JSON.
  6. Baris 36-37 (uji(mux, ...)): menguji kedua rute; helper uji memanggil h.ServeHTTP(rec, req) sehingga mux memproses request persis seperti server sungguhan.
  7. Baris 40-45 (func uji(...)): membuat request tiruan dan perekam, menjalankan handler, lalu mencetak status, header Content-Type, dan body hasil perekaman.

Server Anda kini hidup — tinggal mengatur rute yang rapi agar aplikasi bisa tumbuh. Chapter berikutnya membahas pola routing method dan path yang diperkenalkan ServeMux sejak Go 1.22.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

server-demo.go

Unduh berkas
go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

type Item struct {
	ID   int    `json:"id"`
	Nama string `json:"nama"`
}

// haloHandler memiliki signature handler: dua parameter w dan r.
func haloHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprintln(w, "Halo dari handler")
}

func main() {
	mux := http.NewServeMux()

	// Fungsi biasa otomatis menjadi handler lewat mux.HandleFunc.
	mux.HandleFunc("/halo", haloHandler)

	// Handler anonim: atur header, status code, lalu tulis body JSON.
	mux.HandleFunc("/items", func(w http.ResponseWriter, r *http.Request) {
		items := []Item{{ID: 1, Nama: "Buku Go"}, {ID: 2, Nama: "Mouse"}}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(items)
	})

	// Uji handler tanpa membuka port: request tiruan + perekam response.
	uji(mux, http.MethodGet, "/halo")
	uji(mux, http.MethodGet, "/items")
}

func uji(h http.Handler, method, path string) {
	req := httptest.NewRequest(method, path, nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	fmt.Printf("%s %s -> %d (%s)\n", method, path, rec.Code, rec.Header().Get("Content-Type"))
	fmt.Printf("  body: %s", rec.Body.String())
}