HTTP Secara Mendalam

Metode, kode status, header, keep-alive, dan pengenalan HTTP/2 sebagai kontrak lalu lintas web.

Daftar isi

HTTP (Hypertext Transfer Protocol) adalah protokol yang mengatur pertukaran pesan antara browser dan server, sehingga menjadi kontrak lalu lintas web yang dibaca oleh setiap desain sistem. Topik ini membedah anatomi pesan permintaan (request) dan respons (response), semantik metode dan kode status, peran header, mekanisme keep-alive, hingga pengenalan HTTP/2. Setelah membacanya, keputusan seperti pemilihan metode, penetapan kode status, dan estimasi biaya koneksi dapat diambil dengan dasar yang terukur.

Anatomi pesan permintaan dan respons#

Setiap interaksi HTTP terdiri atas dua pesan, yaitu permintaan dari klien dan respons dari server. Pesan permintaan dimulai dari baris awal yang memuat metode, path, dan versi protokol, dilanjutkan dengan rangkaian header, satu baris kosong, lalu tubuh pesan bila ada. Respons memakai struktur yang sama, tetapi baris awalnya memuat versi protokol, kode status, dan teks keterangan singkat. Contoh berikut memperlihatkan satu pertukaran lengkap untuk membuat catatan baru.

http
POST /catatan HTTP/1.1
Host: catatan.example
Content-Type: application/json
Content-Length: 50

{"judul": "Belanja mingguan", "isi": "Telur 1 kg"}

HTTP/1.1 201 Created
Content-Type: application/json
Location: /catatan/12
Content-Length: 60

{"id": 12, "judul": "Belanja mingguan", "isi": "Telur 1 kg"}

Baris Content-Length: 50 menyatakan panjang tubuh pesan dalam byte, sehingga penerima tahu kapan tubuh pesan selesai dibaca. Kode 201 Created menyatakan catatan baru berhasil dibuat, sedangkan header Location menunjuk alamat catatan tersebut. Nama header tidak peka terhadap huruf besar-kecil, dan tanda titik dua memisahkan nama header dari nilainya. Seluruh pesan HTTP/1.1 berupa teks biasa yang dapat dibaca langsung, dan struktur inilah yang diuraikan oleh setiap server, proxy, dan cache di sepanjang jalurnya.

Metode dan semantiknya#

Metode menyatakan tindakan yang diminta klien atas sebuah resource, sedangkan resource adalah objek yang dialamatkan oleh path. Dua sifat yang menentukan kebolehan mengulang permintaan adalah aman (safe) dan idempoten (idempotent). Metode aman tidak mengubah kondisi server, sedangkan metode idempoten menghasilkan efek yang sama meskipun dikirim berkali-kali. Sifat ini penting ketika permintaan gagal karena timeout, yaitu ketika jawaban tidak kunjung tiba dalam batas waktu yang disepakati. Pengulangan GET atau PUT tidak berbahaya pada situasi itu, sedangkan POST yang diulang dapat membuat dua catatan sekaligus.

MetodeSemantikAmanIdempoten
GETMembaca resource tanpa mengubahnyaYaYa
POSTMembuat data baru atau memicu prosesTidakTidak
PUTMengganti resource secara utuhTidakYa
PATCHMengubah sebagian kolom resourceTidakTidak
DELETEMenghapus resourceTidakYa

Tabel di atas adalah kontrak yang dibaca bukan hanya oleh aplikasi, tetapi juga oleh cache, load balancer, dan mesin pencari. Cache umumnya hanya menyimpan jawaban GET, sedangkan permintaan tidak aman selalu diteruskan ke server. Konvensi ini menjadikan pemilihan metode sebagai keputusan desain, bukan sekadar selera penamaan, karena alat di sekitar API mengandalkan semantik tersebut.

Kode status dan kelasnya#

Kode status adalah angka tiga digit yang menyatakan hasil permintaan, dan digit pertamanya menentukan kelasnya. Kelas 2xx menyatakan keberhasilan, 3xx pengalihan, 4xx kesalahan di sisi klien, dan 5xx kegagalan di sisi server. Kelas 1xx jarang terlihat pada aplikasi biasa karena berupa sinyal perantara seperti 100 Continue. Beberapa kode yang paling sering ditemui dirangkum pada tabel berikut.

KodeMaknaKegunaan lazim
200 OKPermintaan berhasilJawaban GET atau PUT
201 CreatedResource baru dibuatJawaban POST yang berhasil
204 No ContentBerhasil tanpa tubuh jawabanPenghapusan tanpa data kembali
301, 302Pengalihan permanen atau sementaraPerpindahan alamat
304 Not ModifiedCache masih segarEfisiensi baca berulang
400 Bad RequestPesan tidak dapat diuraikanIsi permintaan tidak valid
401, 403Belum terautentikasi atau akses ditolakGerbang otorisasi
404 Not FoundResource tidak ditemukanAlamat keliru atau data dihapus
429 Too Many RequestsBatas laju terlampauiSinyal untuk rate limiting
500, 502, 503, 504Kegagalan di sisi server atau perantaraDiagnosa arsitektur

Pada desain sistem, kode status berfungsi sebagai sinyal bagi mesin, bukan teks untuk manusia. Cache dan CDN (content delivery network) memutuskan penyimpanan berdasarkan kode status bersama header caching, sistem pemantauan menghitung rasio 5xx sebagai indikator kesehatan, dan health check pada load balancer membaca kode status untuk menentukan server yang layak menerima trafik. Karena itu kode status dipilih berdasarkan penyebab sebenarnya, misalnya 404 untuk resource yang memang tidak ada dan 403 untuk resource yang ada tetapi aksesnya ditolak.

Header sebagai metadata#

Header membawa metadata berpasangan nama-nilai pada setiap pesan, dan hampir seluruh perilaku lanjutan HTTP ditentukan di sana. Header Host menyatakan domain yang dituju sehingga satu server dapat melayani banyak domain, sedangkan Content-Type menjelaskan format tubuh pesan. Header Authorization membawa token autentikasi, dan Cache-Control mengatur berapa lama jawaban boleh disimpan oleh cache. Untuk kesehatan sistem, dua header yang paling sering menentukan adalah Cache-Control dan Retry-After.

Header Cache-Control yang benar mengurangi beban server tanpa mengubah kebenaran data, sedangkan yang keliru menyebarkan data basi ke seluruh lapisan cache. Header Retry-After menyertai kode 429 atau 503 dan menyatakan jeda minimum sebelum mencoba ulang, sehingga klien yang patuh memberi ruang bernapas bagi server yang sedang kelebihan beban. Kesalahan memilih nilai keduanya tidak terlihat saat pengujian lokal dan baru muncul pada skala production.

Header juga menjadi tempat negosiasi kemampuan antara klien dan server. Header Accept menyatakan format yang dapat diuraikan klien, sedangkan Accept-Encoding menawarkan kompresi seperti gzip. Pola negosiasi ini memungkinkan satu endpoint melayani beragam klien tanpa mengubah alamatnya, sehingga kontrak API tetap stabil ketika perangkat lunaknya berevolusi.

Keep-alive dan biaya koneksi#

Setiap koneksi TCP baru membayar satu handshake, yaitu serangkaian pesan pembuka koneksi, sebelum byte pertama terkirim. Pada HTTP/1.0, koneksi ditutup setelah satu respons, sehingga setiap permintaan membayar handshake baru lagi. HTTP/1.1 menjadikan koneksi persisten atau keep-alive sebagai perilaku default, yaitu satu koneksi TCP dipakai ulang untuk banyak permintaan. Biayanya mudah dihitung per permintaan dengan dua rumus berikut, dengan RTT (round-trip time) sebagai waktu tempuh satu pulang-pergi jaringan dan asumsi tanpa TLS.

Tkoneksi baru=2×RTT+Tproses T_{\text{koneksi baru}} = 2 \times \text{RTT} + T_{\text{proses}}
Tkeep-alive=RTT+Tproses T_{\text{keep-alive}} = \text{RTT} + T_{\text{proses}}

Skrip berikut menghitung total waktu 100 permintaan dengan asumsi RTT 50 ms dan waktu proses server 10 ms. Jalankan skripnya dengan python3 keepalive.py, lalu bandingkan kedua baris totalnya. Selisihnya tumbuh linier terhadap jumlah permintaan karena kekurangan satu RTT dibayar pada setiap permintaan.

python keepalive.py
# Perbandingan waktu total 100 permintaan HTTP/1.1 pada koneksi yang selalu
# baru versus koneksi persisten (keep-alive).
# Asumsi: RTT 50 ms, waktu proses server 10 ms per permintaan, tanpa TLS,
# sehingga pembentukan koneksi hanya membayar satu handshake TCP.

RTT = 50     # ms, waktu tempuh satu pulang-pergi jaringan
PROSES = 10  # ms, waktu server memproses satu permintaan
N = 100      # jumlah permintaan yang dikirim browser

# Koneksi baru per permintaan: 1 RTT handshake TCP + 1 RTT permintaan-respons
waktu_koneksi_baru = N * (2 * RTT + PROSES)

# Keep-alive: handshake dibayar sekali, tiap permintaan 1 RTT + proses
waktu_keep_alive = RTT + N * (RTT + PROSES)

hemat = waktu_koneksi_baru - waktu_keep_alive
persen = 100 * hemat / waktu_koneksi_baru

print(f"Jumlah permintaan           : {N}")
print(f"RTT                         : {RTT} ms")
print(f"Waktu proses server         : {PROSES} ms")
print(f"Koneksi baru per permintaan : {waktu_koneksi_baru} ms")
print(f"Keep-alive                  : {waktu_keep_alive} ms")
print(f"Penghematan                 : {hemat} ms ({persen:.0f}%)")
text Keluaran
Jumlah permintaan           : 100
RTT                         : 50 ms
Waktu proses server         : 10 ms
Koneksi baru per permintaan : 11000 ms
Keep-alive                  : 6050 ms
Penghematan                 : 4950 ms (45%)

Dengan asumsi di atas, keep-alive menghemat hampir separuh waktu total. Angka persentasenya berubah mengikuti RTT: jaringan dengan RTT besar memperbesar penghematan, sedangkan RTT kecil mengecilkan perbedaannya. Pada praktiknya penghematan lebih besar lagi karena pembentukan koneksi juga menambah beban CPU dan memori pada server yang melayani puluhan ribu koneksi.

Pengenalan HTTP/2#

HTTP/1.1 memproses satu permintaan pada satu koneksi dalam satu waktu, sehingga permintaan berikutnya menunggu respons sebelumnya selesai. Kondisi menunggu ini disebut head-of-line blocking pada lapisan HTTP. Pemecahan lazimnya adalah membuka banyak koneksi paralel, tetapi setiap koneksi membayar lagi handshake dan memori di sisi server.

HTTP/2 menyelesaikannya dengan binary framing, yaitu pesan dipecah menjadi bingkai kecil, dan bingkai dari banyak permintaan disisipkan bergantian di dalam satu koneksi. Mekanisme ini disebut multiplexing, sehingga satu koneksi cukup untuk semua permintaan sekaligus. HTTP/2 juga memampatkan header dengan HPACK, yaitu algoritma kompresi khusus untuk header HTTP/2. Pemampatan ini penting karena header yang sama dikirim ulang pada setiap permintaan dan memakan bandwidth, yaitu kapasitas koneksi yang tersedia. Ilustrasi berikut membandingkan antrean keduanya, dengan A, B, dan C sebagai tiga permintaan serta angka yang menandai urutan bingkainya.

text Contoh: antrean HTTP/1.1 dan multiplexing HTTP/2
HTTP/1.1 : [A ................][B ................][C ................]
HTTP/2   : [A1][B1][A2][C1][B2][A3][C2] berbagi satu koneksi yang sama

Kelemahan pada koneksi TCP tetap melekat pada HTTP/2, yaitu satu paket yang hilang menahan seluruh bingkai di belakangnya pada lapisan transport. Kelemahan inilah yang memotivasi protokol QUIC, transport terenkripsi di atas UDP yang menjadi dasar HTTP/3, untuk memindahkan multiplexing ke lapisan transport. Perbandingan transport tersebut dibedah secara penuh pada topik tentang TCP dan UDP di modul ini.

Kesalahan umum#

Beberapa kesalahan berikut muncul berulang ketika kontrak HTTP dirancang tanpa membaca semantiknya.

  • Menggunakan GET untuk operasi yang mengubah data. Alat seperti crawler, yaitu program yang menjelajah halaman secara otomatis, dan fitur prefetch browser dapat memicu GET tanpa niat pengguna, sehingga perubahan terjadi tanpa disengaja. Operasi yang mengubah data wajib memakai metode tidak aman seperti POST.
  • Mengabaikan Retry-After pada respons 429 dan 503. Klien yang mengulang seketika memperberat server yang sedang kelebihan beban, sedangkan jeda pada header itu menyatakan batas minimum sebelum mencoba lagi.
  • Menyamakan teks kode status dengan perilaku yang pasti. Kontraknya adalah angkanya, sedangkan teks pendamping seperti Gateway Timeout hanyalah keterangan yang dapat berbeda antarimplementasi.

Ringkasan#

  • Pesan HTTP terdiri atas baris awal, header, baris kosong, dan tubuh opsional, dengan bentuk yang sama untuk permintaan dan respons.
  • Metode menyatakan semantik tindakan dan kode status menyatakan hasilnya, sedangkan sifat aman dan idempoten menentukan kebolehan mengulang permintaan saat timeout.
  • Header membawa metadata yang menggerakkan caching, autentikasi, dan negosiasi format, sehingga keputusan cache dan pemantauan berdiri di atasnya.
  • Keep-alive menghemat satu RTT per permintaan, dan HTTP/2 melanjutkan penghematan itu dengan multiplexing banyak aliran pada satu koneksi.

Referensi#