Topik ini membahas http-kit, yaitu pustaka server HTTP untuk Clojure yang bekerja dengan model berbasis event dan jumlah thread yang sedikit. Pembahasan mencakup run-server beserta fungsi stop yang dikembalikannya, konfigurasi port, siklus hidup server yang diuji dalam satu blok kode, serta dasar WebSocket untuk komunikasi dua arah. Setelah topik ini selesai, handler Ring dapat dilayani melalui HTTP sungguhan dan diuji tanpa terminal terpisah.
run-server dan fungsi stop#
Server http-kit dimulai dengan fungsi org.httpkit.server/run-server. Fungsi ini menerima handler Ring biasa beserta map konfigurasi, dan kunci yang paling penting adalah :port. Nilai kembalinya bukan objek server semata, melainkan fungsi tanpa argumen yang menghentikan server ketika dipanggil. Pola (def stop (server/run-server app {:port 18080})) membuat penghentian server menjadi bagian dari kode aplikasi, bukan langkah manual di terminal.
Model kerja http-kit berbeda dari server berbasis thread per koneksi. http-kit mengelola sejumlah kecil thread worker dan melayani banyak koneksi serentak dengan mekanisme non-blocking pada tingkat Java NIO. Dengan pendekatan itu, throughput, yakni jumlah permintaan yang dilayani per satuan waktu, tetap tinggi ketika koneksi bertambah. Konsekuensinya, pekerjaan yang membekukan thread worker berdampak besar terhadap seluruh server, dan batasan itu dibahas pada bagian Kesalahan umum.
Konfigurasi lain yang sering disetel meliputi :thread untuk jumlah worker dan :ip untuk antarmuka jaringan yang diikat. Untuk pengujian lokal, port yang jarang terpakai seperti 18080 menghindarkan bentrokan dengan layanan lain yang sudah berjalan pada mesin yang sama.
Dua pustaka yang digunakan pada topik ini dinyatakan pada berkas deps.edn di akar direktori proyek. Berkas deps.edn pada bundle topik ini memuat dua koordinat, yaitu http-kit/http-kit untuk server dan client, serta ring/ring-core untuk kontrak handler yang digunakan di sepanjang topik. Dengan berkas tersebut, pemanggilan clojure -M berkas.clj dari dalam direktori topik sudah menyiapkan classpath lengkap tanpa penyetelan tambahan.
{:deps {http-kit/http-kit {:mvn/version "2.8.1"}
ring/ring-core {:mvn/version "1.15.6"}}}Siklus hidup server dalam satu blok uji#
Kombinasi server dan HTTP client dalam satu proses membuat pengujian layanan web menjadi deterministik. Pustaka http-kit menyediakan client asinkron di namespace org.httpkit.client, dan fungsi get miliknya mengembalikan promise, yaitu wadah nilai yang terisi ketika responsnya tiba. Tanda @ menunggu promise terisi sebelum nilainya dibaca, sehingga hasil responsenya dapat dicetak dengan println.
Contoh berikut menjalankan server pada port 18080, memanggil dua jalur dengan client, mencetak status dan body-nya, lalu menghentikan server dengan fungsi stop yang disimpan sejak awal.
(require '[org.httpkit.server :as server]
'[org.httpkit.client :as http])
(def app
(fn [request]
(if (= "/katalog" (:uri request))
{:status 200
:headers {"Content-Type" "text/plain; charset=utf-8"}
:body "katalog perpustakaan: 3 judul"}
{:status 404
:headers {}
:body "halaman tidak ditemukan"})))
(def stop (server/run-server app {:port 18080}))
(let [respons @(http/get "http://127.0.0.1:18080/katalog")]
(println "Status:" (:status respons))
(println "Body:" (:body respons)))
(let [respons @(http/get "http://127.0.0.1:18080/rusak")]
(println "Status:" (:status respons))
(println "Body:" (:body respons)))
(stop)
(println "server dihentikan")Status: 200
Body: katalog perpustakaan: 3 judul
Status: 404
Body: halaman tidak ditemukan
server dihentikanHandler yang digunakan adalah handler Ring polos, dan bentuknya tidak berubah sama sekali ketika dilayani oleh server nyata. Respons pertama menunjukkan jalur /katalog dilayani dengan status 200, respons kedua menunjukkan jalur yang tidak dikenal berujung pada status 404, dan pesan terakhir memastikan fungsi stop telah dijalankan.
Dasar WebSocket#
Selain HTTP, http-kit menyediakan dukungan WebSocket, yaitu protokol komunikasi dua arah yang mempertahankan satu koneksi pada soket yang sama. Upgrade dari HTTP ke WebSocket ditangani oleh macro with-channel. Macro ini mengikat koneksi ke sebuah simbol, dan selama koneksi terbuka, dua pendaftaran utama digunakan, yaitu on-receive yang dipanggil setiap pesan dari klien tiba, serta on-close yang dipanggil ketika koneksi berakhir beserta status penyebabnya. Pengiriman pesan balik dilakukan dengan send!.
Contoh berikut berupa papan pesan sederhana pada antrean loket perpustakaan. Setiap pesan yang tiba dikirim kembali dengan awalan tetap, dan penutupan koneksi dicatat ke layar. Kode ini membutuhkan server yang berjalan serta klien WebSocket sungguhan, sehingga hanya dipaparkan sebagai pola, bukan dijalankan pada REPL.
; tidak-dijalankan
(require '[org.httpkit.server :as server])
(defn handler-papan [request]
(server/with-channel request channel
(server/on-receive channel
(fn [pesan]
(server/send! channel (str "papan loket: " pesan))))
(server/on-close channel
(fn [status]
(println "koneksi ditutup:" status)))))Ketika klien terhubung dan mengirim pesan “nomor 12”, koneksi yang sama menerima balasan “papan loket: nomor 12” tanpa membuka koneksi baru. Handler seperti ini tetap merupakan handler Ring, sehingga dapat dipasang pada rute tertentu bersama handler HTTP biasa. Pola ini cukup untuk membuka komunikasi dua arah, dan kebutuhan yang lebih rumit seperti pendaftaran pemancar ganda ditangani dengan menyimpan channel pada atom per ruang obrolan.
Kesalahan umum#
Menjalankan pekerjaan blocking di dalam handler. Pemanggilan yang membekukan thread, seperti operasi jaringan sinkron atau perulangan berat, menahan salah satu dari sedikit thread worker http-kit dan menurunkan throughput seluruh server. Perbaikannya dengan memindahkan pekerjaan berat ke thread terpisah, misalnya lewat channel core.async.
Memanggil run-server dua kali pada port yang sama tanpa menghentikan server sebelumnya. Pemanggilan kedua melempar error karena port 18080 sudah diikat oleh server yang sedang berjalan. Perbaikannya dengan menjalankan fungsi stop terlebih dahulu, atau merancang blok uji agar penghentian selalu menjadi baris terakhirnya.
Membaca hasil client tanpa deref. Fungsi http/get mengembalikan promise yang belum terisi, dan mencetak objek promise itu langsung menghasilkan alamat objek, bukan isi responsenya. Perbaikannya dengan membaca nilai melalui @ atau deref, dengan timeout tambahan apabila pembacaan tidak boleh menunggu tanpa batas.
Ringkasan#
org.httpkit.server/run-servermenjalankan server Ring pada port tertentu dan mengembalikan fungsi tanpa argumen yang menghentikannya.- http-kit melayani banyak koneksi dengan sedikit thread worker berbasis event, sehingga throughput tinggi tercapai tanpa thread per koneksi.
- Siklus hidup server dapat diuji dalam satu blok kode, yaitu menjalankan server, memanggilnya dengan client bawaan melalui promise yang di-deref, mencetak hasilnya, lalu menghentikan server.
- WebSocket ditangani dengan
with-channel,on-receive,on-close, dansend!untuk komunikasi dua arah yang mempertahankan satu koneksi pada soket yang sama.
Referensi#
- http-kit — GitHub — Repositori resmi http-kit beserta dokumentasi server, client, dan WebSocket.
- Ring — GitHub — Spesifikasi handler Ring yang dilayani oleh server http-kit.