Server dalam beberapa baris
Handler adalah fungsi yang menerima request dan menulis response. ServeMux meneruskan request ke handler yang tepat.
Editor tidak bisa menerima koneksi dari browser-mu, jadi contoh di halaman ini menjalankan server dengan httptest.NewServer dan memanggilnya dari program yang sama. Di program sungguhan, bagian terakhir diganti dengan satu baris yang memblokir dan melayani request selamanya:
log.Fatal(http.ListenAndServe(":8080", mux))
Setelah itu curl localhost:8080/hello/gopher mencetak Hello, gopher!. ListenAndServe hanya kembali saat terjadi error (misalnya port sudah dipakai), karena itulah ia dibungkus dengan log.Fatal.
Setiap request berjalan di goroutine-nya sendiri. Apa pun yang dipakai bersama oleh handler-mu, seperti map atau counter, butuh mutex.
Handler
Apa pun yang punya method ServeHTTP(http.ResponseWriter, *http.Request) adalah http.Handler. http.HandlerFunc menyesuaikan fungsi biasa ke interface itu, dan mux.HandleFunc melakukan konversinya untukmu. Handler berupa struct berguna saat handler membutuhkan dependensi:
type API struct {
db *sql.DB
}
func (a *API) listItems(w http.ResponseWriter, r *http.Request) { /* uses a.db */ }
mux.HandleFunc("GET /items", api.listItems)
*http.Request memberimu:
| Field atau method | Isinya |
|---|---|
r.Method | GET, POST, ... |
r.URL.Path | path-nya, /items/42 |
r.PathValue("id") | wildcard dari pola route (Go 1.22) |
r.URL.Query().Get("q") | parameter query string |
r.Header.Get("Authorization") | header request |
r.Body | body request, sebuah io.ReadCloser (server yang menutupnya) |
r.FormValue("name") | field form atau parameter query |
r.Context() | context yang dibatalkan saat client memutus koneksi |
Pola routing (Go 1.22)
Sejak Go 1.22, pola ServeMux berbentuk [METHOD ][HOST]/[PATH], dan path boleh berisi wildcard.
| Pola | Cocok dengan |
|---|---|
"/items/" | /items/ dan semua yang ada di bawahnya (garis miring di akhir = prefix) |
"/items" | hanya /items |
"GET /items/{id}" | GET (dan HEAD) pada /items/42; r.PathValue("id") == "42" |
"POST /items" | hanya POST pada /items |
"/files/{path...}" | /files/a/b/c; path bernilai "a/b/c" |
"/{$}" | hanya /, bukan setiap path |
"/" | setiap path yang tidak cocok dengan pola lain |
Saat dua pola sama-sama cocok, pola yang lebih spesifik menang, jadi /items/new mengalahkan /items/{id}. Kalau tidak ada yang lebih spesifik, misalnya /items/{id} dan /{kind}/new (keduanya cocok dengan /items/new), mendaftarkan pola kedua akan panic dengan pesan yang menyebut kedua pola. Kalau path-nya cocok tapi method-nya tidak, mux menjawab 405 Method Not Allowed dengan header Allow, tanpa kode apa pun darimu.
Pola "/" adalah penangkap semua path. Ini mengejutkan orang yang mendaftarkan halaman beranda dengan "/" lalu mendapati halaman itu menjawab setiap URL tak dikenal dengan 200. Pakai "GET /{$}" untuk halaman beranda.
Pola-pola ini butuh go 1.22 atau lebih baru di go.mod. Dengan baris versi yang lebih lama, mux kembali ke perilaku lama: ia membaca "GET /items" sebagai nama host diikuti path, sehingga route-nya tidak pernah cocok dan setiap request ke /items mendapat 404.
API JSON kecil
Request terakhir memakai DELETE, yang tidak diterima oleh route mana pun, dan mux dengan sendirinya menjawab 405 dengan Allow: GET, HEAD: itulah method yang terdaftar untuk path tersebut (route GET juga menerima HEAD).
Kode status dan urutan penulisan
Response punya tiga bagian, dan ketiganya harus ditulis berurutan: header, status, body.
w.Header().Set(...)mengubah header. Pengaruhnya hanya berlaku sampai status dikirim.w.WriteHeader(code)mengirim baris status dan header.w.Write(...)(ataufmt.Fprint(w, ...), atau sebuah encoder) mengirim body. KalauWriteHeaderbelum dipanggil,Writepertama akan mengirim200 OKlebih dulu.
Akibatnya:
- Mengatur header setelah body mulai dikirim tidak berpengaruh apa-apa.
- Memanggil
WriteHeaderdua kali mencatathttp: superfluous response.WriteHeader calldi log dan mempertahankan status pertama. - Setelah response error, lakukan
return.http.Errortidak menghentikan handler-mu, dan kode setelahnya tetap menulis ke response yang sama.
Pakai konstanta bernama (http.StatusOK, http.StatusCreated, http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError) daripada angka polos. http.StatusText(404) mengembalikan "Not Found".
Middleware
Middleware adalah fungsi yang menerima handler dan mengembalikan handler, yang melakukan sesuatu sebelum atau sesudah memanggil handler berikutnya:
Baris log: selalu muncul sebelum client got: untuk request yang sama. Goroutine handler mencetaknya sebelum kembali, dan server tidak menyelesaikan response sampai handler kembali.
statusRecorder menyematkan (embed) ResponseWriter yang asli dan hanya meng-override WriteHeader, yang merupakan cara standar untuk mengamati kode status dari middleware.
Pengaturan untuk production
http.ListenAndServe memakai server tanpa timeout, jadi client yang lambat bisa menahan koneksi terbuka tanpa batas waktu. Konfigurasikan http.Server secara eksplisit untuk apa pun yang terbuka ke internet, dan matikan dengan rapi (graceful):
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done() // wait for Ctrl+C or a SIGTERM from the orchestrator
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil { // finish in-flight requests
log.Println("shutdown:", err)
}
Shutdown berhenti menerima koneksi baru dan menunggu request yang sedang berjalan selesai, sampai batas deadline context. ListenAndServe mengembalikan http.ErrServerClosed begitu Shutdown dimulai, karena itulah error tersebut tidak dianggap sebagai kegagalan. Untuk handler yang berjalan lama, teruskan r.Context() ke bawah supaya handler berhenti saat client pergi.
Menyajikan file statis cukup satu baris: mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public")))).
Kesalahan umum
- Tidak melakukan return setelah
http.Error. Handler terus berjalan dan menulis lebih banyak ke response. - Mengatur header setelah menulis body. Header itu dibuang diam-diam.
- Mendaftarkan halaman beranda di
"/". Halaman itu menjadi penangkap semua path yang tidak dikenal. Pakai"/{$}". - Berbagi state antar handler tanpa lock. Request berjalan secara concurrent.
- Memakai server default di production. Server itu tidak punya timeout; atur timeout-nya di
http.Server. - Pola routing diabaikan. Kalau
"GET /x/{id}"tidak pernah cocok, pastikango.modmenulisgo 1.22atau lebih baru.
Pertanyaan yang sering diajukan
Bagaimana cara membuat web server sederhana di Go?
Daftarkan handler lalu mulai mendengarkan koneksi: http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello") }) lalu log.Fatal(http.ListenAndServe(":8080", nil)). Server dari standard library sudah layak untuk production; ia menangani HTTP/1.1, HTTP/2 lewat TLS, keep-alive, dan satu goroutine per koneksi.
Bagaimana cara mengambil parameter path di net/http Go?
Sejak Go 1.22, pola ServeMux boleh berisi wildcard: daftarkan mux.HandleFunc("GET /items/{id}", h) lalu baca nilainya di dalam handler dengan r.PathValue("id"). {path...} di bagian akhir mencocokkan sisa path. Sebelum 1.22, kamu harus memecah r.URL.Path sendiri atau memakai router seperti chi.
Apakah saya perlu framework seperti Gin untuk membuat REST API di Go?
Tidak. Sejak Go 1.22, net/http bisa melakukan routing berdasarkan method dan parameter path, yang dulu menjadi alasan utama orang memakai router. Dengan encoding/json untuk body dan fungsi middleware kecil untuk logging dan auth, standard library sudah cukup untuk kebanyakan API. Framework menambahkan kemudahan seperti binding dan validasi request.
Bagaimana cara mengatur kode status di HTTP handler Go?
Panggil w.WriteHeader(http.StatusCreated) sebelum menulis body. Kalau body ditulis lebih dulu, Go otomatis mengirim 200 OK dan WriteHeader yang dipanggil setelahnya diabaikan dengan pesan log superfluous response.WriteHeader call. Atur header dengan w.Header().Set sebelum WriteHeader juga; untuk error, http.Error(w, msg, code) mengerjakan semuanya.