Handler yang baik menulis dan membaca dengan sama rajinnya: query parameter dari URL, header dari klien, body JSON dari aplikasi, sampai data form dari halaman HTML. Package net/http menyediakan alat baca untuk semuanya, dan package encoding/json mengubah aliran body menjadi struct Go. Pada chapter ini kita akan membaca seluruh jenis input tersebut, membatasi ukuran body dengan MaxBytesReader, lalu membalasnya dengan helper penulis JSON yang rapi. Seluruh handler diuji lewat request tiruan httptest sehingga hasil eksekusi deterministik.
Query Parameter #
Query parameter tersimpan di r.URL.Query() yang mengembalikan url.Values — map bertipe map[string][]string:
q := r.URL.Query()
kata := q.Get("q") // nilai pertama, "" bila tidak ada
tags := q["tag"] // semua nilai, slice kosong bila tidak adaMethod Get mengembalikan nilai pertama untuk kunci tersebut, sedangkan akses langsung q["tag"] mengembalikan seluruh nilai — parameter yang muncul dua kali seperti ?tag=buku&tag=net hanya bisa dibaca utuh lewat bentuk kedua. Untuk URL ?q=go&tag=buku&tag=net, q.Get("q") menghasilkan "go" dan q["tag"] menghasilkan slice ["buku", "net"].
Header Request #
Header request dibaca melalui r.Header yang juga berbentuk map[string][]string:
reqID := r.Header.Get("X-Request-Id")Method Get mengembalikan nilai pertama dari header bernama tersebut. Standard library menormalkan nama header ke bentuk canonical — X-Request-Id, bukan x-request-id — sehingga pemanggilan Get tidak memedulikan besar kecil huruf. Header tambahan seperti ini lazim dipakai untuk menelusuri request antar layanan.
Body JSON dan Pembatas Ukuran #
Body request dibaca sebagai aliran pada r.Body. Untuk body JSON, pola idiomatiknya adalah decoder langsung dari aliran:
var item Item
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, "body tidak valid: "+err.Error(), http.StatusBadRequest)
return
}Fungsi json.NewDecoder(r.Body).Decode(&item) mengalirkan body langsung ke struct tanpa perantara []byte. Kegagalan decode — body bukan JSON, misalnya — memicu respons 400 melalui helper http.Error. Server produksi juga membatasi ukuran body agar klien nakal tidak mengirim muatan raksasa:
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)Pembungkusan http.MaxBytesReader membatasi r.Body hingga 1 MiB; pembacaan yang melampaui batas menghasilkan error http: request body too large. Handler level middleware bisa juga dibungkus sekali dengan http.MaxBytesHandler.
Form dengan r.FormValue #
Data form application/x-www-form-urlencoded — format bawaan form HTML — dibaca dengan r.FormValue:
nama := r.FormValue("nama")Method tersebut mem-parse body form dan query sekaligus. Apabila kunci yang sama muncul di dua tempat, nilai dari body form menang atas nilai dari query — detail yang terbukti pada program contoh, di mana query [email protected] kalah oleh body [email protected].
Menulis Response JSON #
Respons JSON yang rapi selalu mengikuti urutan tiga langkah: set Content-Type, kirim status, lalu encode:
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}Helper seperti writeJSON di atas layak dijadikan fungsi bersama untuk seluruh handler, sehingga setiap endpoint konsisten. Untuk kegagalan, helper http.Error menulis body teks, status, dan Content-Type: text/plain; charset=utf-8 sekaligus. Pilihan status yang lazim: 201 setelah data berhasil dibuat, 400 untuk input tidak valid, dan 404 saat resource tidak ditemukan.
Program Utuh: Empat Jenis Input #
Berkas request-demo.go pada bagian bawah halaman mendaftarkan tiga handler — /cari, /simpan, dan /daftar — lalu menguji masing-masing dengan request tiruan yang membawa query, header, body JSON, body kelebihan ukuran, dan body form.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
)
type Item struct {
Nama string `json:"nama"`
Harga int `json:"harga"`
}
type HasilCari struct {
Kata string `json:"kata"`
Tags []string `json:"tags"`
ReqID string `json:"reqID"`
}
type Balasan struct {
Pesan string `json:"pesan"`
Nama string `json:"nama"`
}
// writeJSON mengatur header, status code, lalu menulis body JSON.
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func main() {
mux := http.NewServeMux()
// Membaca query parameter (satu nilai dan multi nilai) + header.
mux.HandleFunc("GET /cari", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
hasil := HasilCari{
Kata: q.Get("q"),
Tags: q["tag"],
ReqID: r.Header.Get("X-Request-Id"),
}
writeJSON(w, http.StatusOK, hasil)
})
// Membaca body JSON dengan pembatas ukuran dari MaxBytesReader.
mux.HandleFunc("POST /simpan", func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 16)
var item Item
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, "body tidak valid: "+err.Error(), http.StatusBadRequest)
return
}
writeJSON(w, http.StatusCreated, Balasan{Pesan: "item tersimpan", Nama: item.Nama})
})
// Membaca form application/x-www-form-urlencoded dengan r.FormValue.
mux.HandleFunc("POST /daftar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "terdaftar: %s <%s>", r.FormValue("nama"), r.FormValue("email"))
})
// GET /cari dengan query multi-value dan header custom.
req := httptest.NewRequest("GET", "/cari?q=go&tag=buku&tag=net", nil)
req.Header.Set("X-Request-Id", "req-42")
uji(mux, req)
// POST /simpan dengan body JSON 15 byte (masih di bawah batas).
uji(mux, httptest.NewRequest("POST", "/simpan",
strings.NewReader(`{"nama":"Buku"}`)))
// POST /simpan dengan body 32 byte: melebihi batas MaxBytesReader.
uji(mux, httptest.NewRequest("POST", "/simpan",
strings.NewReader(`{"nama":"Buku Go","harga":50000}`)))
// POST /daftar dengan form + query yang bernama sama.
form := strings.NewReader("nama=Budi&email=budi%40example.com")
reqDaftar := httptest.NewRequest("POST", "/[email protected]", form)
reqDaftar.Header.Set("Content-Type", "application/x-www-form-urlencoded")
uji(mux, reqDaftar)
}
func uji(h http.Handler, req *http.Request) {
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
fmt.Printf("%s %s -> %d (%s)\n", req.Method, req.RequestURI, rec.Code,
rec.Header().Get("Content-Type"))
fmt.Printf(" body: %s\n", strings.TrimSpace(rec.Body.String()))
}Jalankan berkas tersebut dengan perintah berikut dari direktori chapter:
go run request-demo.goOutput yang Dihasilkan:
GET /cari?q=go&tag=buku&tag=net -> 200 (application/json)
body: {"kata":"go","tags":["buku","net"],"reqID":"req-42"}
POST /simpan -> 201 (application/json)
body: {"pesan":"item tersimpan","nama":"Buku"}
POST /simpan -> 400 (text/plain; charset=utf-8)
body: body tidak valid: http: request body too large
POST /[email protected] -> 200 (text/plain; charset=utf-8)
body: terdaftar: Budi <[email protected]>Mari kita telaah baris demi baris program di atas:
- Baris 28-32 (
func writeJSON(...)): helper penulis response JSON dengan urutan benar — header, status, lalu encode data. - Baris 38-46 (
mux.HandleFunc("GET /cari", ...)): membaca queryqlewatq.Get, seluruh nilaitaglewatq["tag"], dan headerX-Request-Idlewatr.Header.Get, lalu membalas JSON lewatwriteJSON. - Baris 49-57 (
mux.HandleFunc("POST /simpan", ...)): membatasi body ke 16 byte denganhttp.MaxBytesReader, mendecode JSON ke structItem, dan membalas400lewathttp.Errorketika decode gagal. - Baris 60-62 (
mux.HandleFunc("POST /daftar", ...)): membaca formnamadanemaildenganr.FormValue. - Baris 65-67 (
req := httptest.NewRequest(...)dst.): request tiruanGETdengan query multi-value dan header custom. - Baris 70-75 (dua
uji(mux, ...)untuk/simpan): membandingkan body 15 byte yang lolos batas dengan body 32 byte yang ditolakMaxBytesReader. - Baris 78-81 (
form := strings.NewReader(...)dst.): requestPOSTyang membawa body form sekaligus query bernama sama, membuktikan nilai body menang. - Baris 84-89 (
func uji(...)): helper yang menjalankan request tiruan lalu mencetak status,Content-Type, dan body terpangkas.
Handler demi handler kini terbaca dan terbalas dengan rapi. Tugas lintas handler — pencatatan, autentikasi, penangkapan panic — menjadi pekerjaan middleware, pembahasan chapter berikutnya.
Referensi #
- Dokumentasi package net/http
—
Request,ResponseWriter, dan helper response. - Dokumentasi package encoding/json
—
DecoderdanEncoderuntuk body JSON. - Dokumentasi package httptest — request tiruan dan perekam response.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
request-demo.go
Unduh berkaspackage main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
)
type Item struct {
Nama string `json:"nama"`
Harga int `json:"harga"`
}
type HasilCari struct {
Kata string `json:"kata"`
Tags []string `json:"tags"`
ReqID string `json:"reqID"`
}
type Balasan struct {
Pesan string `json:"pesan"`
Nama string `json:"nama"`
}
// writeJSON mengatur header, status code, lalu menulis body JSON.
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func main() {
mux := http.NewServeMux()
// Membaca query parameter (satu nilai dan multi nilai) + header.
mux.HandleFunc("GET /cari", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
hasil := HasilCari{
Kata: q.Get("q"),
Tags: q["tag"],
ReqID: r.Header.Get("X-Request-Id"),
}
writeJSON(w, http.StatusOK, hasil)
})
// Membaca body JSON dengan pembatas ukuran dari MaxBytesReader.
mux.HandleFunc("POST /simpan", func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 16)
var item Item
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, "body tidak valid: "+err.Error(), http.StatusBadRequest)
return
}
writeJSON(w, http.StatusCreated, Balasan{Pesan: "item tersimpan", Nama: item.Nama})
})
// Membaca form application/x-www-form-urlencoded dengan r.FormValue.
mux.HandleFunc("POST /daftar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "terdaftar: %s <%s>", r.FormValue("nama"), r.FormValue("email"))
})
// GET /cari dengan query multi-value dan header custom.
req := httptest.NewRequest("GET", "/cari?q=go&tag=buku&tag=net", nil)
req.Header.Set("X-Request-Id", "req-42")
uji(mux, req)
// POST /simpan dengan body JSON 15 byte (masih di bawah batas).
uji(mux, httptest.NewRequest("POST", "/simpan",
strings.NewReader(`{"nama":"Buku"}`)))
// POST /simpan dengan body 32 byte: melebihi batas MaxBytesReader.
uji(mux, httptest.NewRequest("POST", "/simpan",
strings.NewReader(`{"nama":"Buku Go","harga":50000}`)))
// POST /daftar dengan form + query yang bernama sama.
form := strings.NewReader("nama=Budi&email=budi%40example.com")
reqDaftar := httptest.NewRequest("POST", "/[email protected]", form)
reqDaftar.Header.Set("Content-Type", "application/x-www-form-urlencoded")
uji(mux, reqDaftar)
}
func uji(h http.Handler, req *http.Request) {
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
fmt.Printf("%s %s -> %d (%s)\n", req.Method, req.RequestURI, rec.Code,
rec.Header().Get("Content-Type"))
fmt.Printf(" body: %s\n", strings.TrimSpace(rec.Body.String()))
}