Setiap kali browser menampilkan sebuah halaman web atau aplikasi memanggil API publik, protokol HTTP bekerja sebagai kurir pesan antara klien dan server. Package net/http milik Go menyediakan seluruh peralatan untuk berperan sebagai klien maupun server, tetapi peralatan tersebut jauh lebih mudah dikuasai setelah anatomi protokolnya jelas. Pada chapter ini kita akan membedah URL, request, response, status code, dan sifat stateless HTTP sebagai fondasi seluruh section. Seluruh contoh kode dijalankan secara lokal memakai package net/http/httptest, sehingga Anda tidak memerlukan koneksi internet untuk mengikutinya.

Anatomi URL #

Permintaan HTTP selalu ditujukan ke sebuah alamat berbentuk URL. Sebuah URL memiliki empat bagian utama yang perlu Anda kenali sejak awal:

text
https://api.example.com:8443/v1/items?halaman=2&tag=go
BagianNamaPeran
httpsskema (scheme)protokol yang dipakai; https berarti koneksi terenkripsi TLS
api.example.com:8443hostalamat server beserta port tujuan
/v1/itemspathlokasi sumber daya yang diminta di server
halaman=2&tag=goqueryparameter tambahan berupa pasangan kunci=nilai

Package net/url dari standard library mampu membedah string URL menjadi struct *url.URL yang siap dipakai. Struct tersebut menyimpan field Scheme, Host, Path, dan RawQuery, sementara method Query() mengembalikan nilai bertipe url.Values — sebuah map dengan tipe map[string][]string — sehingga satu parameter query boleh muncul lebih dari satu kali.

Anatomi Request HTTP #

Sebuah request HTTP terdiri dari tiga bagian: baris permintaan (request line), kumpulan header, dan body yang bersifat opsional. Bentuk mentah sebuah request GET tampak seperti berikut:

text
GET /v1/items?halaman=2 HTTP/1.1
Host: api.example.com
Accept: application/json

Baris pertama memuat method, path beserta query, lalu versi protokol. Setelah baris tersebut, header dikirim sebagai pasangan Kunci: Nilai hingga satu baris kosong menjadi penanda akhir header. Method menentukan maksud permintaan:

MethodPeran umumBody request
GETmembaca dataumumnya kosong
POSTmengirim data baru untuk diprosesada
PUTmengganti data secara utuhada
PATCHmengubah sebagian dataada
DELETEmenghapus dataumumnya kosong

Header Content-Type pada request memberi tahu server format body yang dikirim, misalnya application/json untuk data JSON atau application/x-www-form-urlencoded untuk data form HTML.

Status Code dan Anatomi Response #

Server membalas setiap request dengan response yang terdiri dari status line, header, dan body. Bentuk mentah response yang berhasil tampak seperti ini:

text
HTTP/1.1 200 OK
Content-Type: application/json

[{"id":1,"nama":"Buku Go"}]

Status code adalah angka tiga digit yang merangkum hasil request. Lima kelasnya perlu dihafal:

KelasArtiContoh anggota
1xxinformasi protokol, bukan hasil akhir101 Switching Protocols
2xxrequest berhasil200 OK, 201 Created
3xxpengalihan ke lokasi lain301 Moved Permanently, 304 Not Modified
4xxkesalahan pada sisi klien400 Bad Request, 404 Not Found, 405 Method Not Allowed
5xxkesalahan pada sisi server500 Internal Server Error, 503 Service Unavailable

Header Content-Type pada response memberi tahu klien cara membaca body, misalnya application/json atau text/html. Handler Go yang tidak mengatur header ini akan mendeteksi tipe konten secara otomatis dari isi body.

HTTP Bersifat Stateless #

Protokol HTTP bersifat stateless: setiap request berdiri sendiri dan server tidak menyimpan ingatan tentang request sebelumnya. Request kedua dari klien yang sama diperlakukan seperti request dari orang asing. Karena sifat ini, aplikasi web mengirim identitas secara berulang pada setiap request, biasanya lewat header Authorization berisi token atau lewat cookie yang disimpan klien.

Siklus Request dan Response #

Satu putaran lengkap HTTP berjalan sebagai berikut: klien membangun baris permintaan, header, dan body; server menerima request tersebut, handler menulis response; klien membaca response; kemudian koneksi ditutup atau dipakai ulang untuk request berikutnya.

Program: Membedah URL dan Menjalankan Satu Siklus #

Berkas dasar-http-demo.go pada bagian bawah halaman menggabungkan seluruh materi chapter ini: bagian pertama membedah sebuah URL dengan net/url, bagian kedua menjalankan satu siklus request-response di dalam memori.

go
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
)

func main() {
	// Bagian 1: membedah anatomi sebuah URL dengan package net/url.
	alamat := "https://api.example.com:8443/v1/items?halaman=2&tag=go"
	u, err := url.Parse(alamat)
	if err != nil {
		fmt.Println("gagal mem-parse URL:", err)
		return
	}
	fmt.Println("URL     :", alamat)
	fmt.Println("skema   :", u.Scheme)
	fmt.Println("host    :", u.Host)
	fmt.Println("path    :", u.Path)
	fmt.Println("query   :", u.RawQuery)
	fmt.Println("halaman :", u.Query().Get("halaman"))

	// Bagian 2: satu siklus request-response tanpa jaringan nyata.
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Halo, %s!", r.URL.Query().Get("nama"))
	})
	req := httptest.NewRequest(http.MethodGet, "/sapa?nama=Budi", nil)
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	fmt.Println("request :", req.Method, req.RequestURI, req.Proto)
	fmt.Println("respons :", rec.Code, rec.Header().Get("Content-Type"))
	fmt.Println("body    :", rec.Body.String())
}

Jalankan berkas tersebut dengan perintah berikut dari direktori chapter:

bash
go run dasar-http-demo.go

Output yang Dihasilkan:

text
URL     : https://api.example.com:8443/v1/items?halaman=2&tag=go
skema   : https
host    : api.example.com:8443
path    : /v1/items
query   : halaman=2&tag=go
halaman : 2
request : GET /sapa?nama=Budi HTTP/1.1
respons : 200 text/plain; charset=utf-8
body    : Halo, Budi!

Mari kita telaah baris demi baris program di atas:

  1. Baris 13 (u, err := url.Parse(alamat)): mem-parse string URL menjadi struct *url.URL; error dikembalikan apabila format alamat tidak valid.
  2. Baris 19-23 (fmt.Println("skema ... dst.): mencetak field u.Scheme, u.Host, u.Path, dan u.RawQuery satu per satu, lalu u.Query().Get("halaman") mengambil nilai pertama parameter halaman dari query.
  3. Baris 26-28 (handler := http.HandlerFunc(...)): membungkus fungsi anonim menjadi handler; fungsi tersebut membaca parameter query nama lalu menulis sapaan ke w.
  4. Baris 29-30 (req := httptest.NewRequest(...), rec := ...): membuat request tiruan GET /sapa?nama=Budi beserta perekam response yang masih kosong.
  5. Baris 32 (handler.ServeHTTP(rec, req)): menjalankan handler; selama panggilan ini handler membaca req dan menulis status, header, serta body ke rec.
  6. Baris 34-36 (fmt.Println("request ...") dst.): mencetak baris permintaan (req.Method, req.RequestURI, req.Proto), status code hasil perekaman, header Content-Type yang terdeteksi otomatis, dan body hasil perekaman.

Dengan anatomi protokol yang sudah jelas, kita siap berdiri di kedua sisi komunikasi. Chapter berikutnya memulai dari sisi pengirim: mengirim request dengan http.Client dan membaca response dengan rapi.

Referensi #

Berkas Sumber

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

dasar-http-demo.go

Unduh berkas
go
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
)

func main() {
	// Bagian 1: membedah anatomi sebuah URL dengan package net/url.
	alamat := "https://api.example.com:8443/v1/items?halaman=2&tag=go"
	u, err := url.Parse(alamat)
	if err != nil {
		fmt.Println("gagal mem-parse URL:", err)
		return
	}
	fmt.Println("URL     :", alamat)
	fmt.Println("skema   :", u.Scheme)
	fmt.Println("host    :", u.Host)
	fmt.Println("path    :", u.Path)
	fmt.Println("query   :", u.RawQuery)
	fmt.Println("halaman :", u.Query().Get("halaman"))

	// Bagian 2: satu siklus request-response tanpa jaringan nyata.
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Halo, %s!", r.URL.Query().Get("nama"))
	})
	req := httptest.NewRequest(http.MethodGet, "/sapa?nama=Budi", nil)
	rec := httptest.NewRecorder()

	handler.ServeHTTP(rec, req)

	fmt.Println("request :", req.Method, req.RequestURI, req.Proto)
	fmt.Println("respons :", rec.Code, rec.Header().Get("Content-Type"))
	fmt.Println("body    :", rec.Body.String())
}