Modul http mentah

createServer dan siklus hidup permintaan serta respons pada modul http.

Daftar isi

Modul http adalah modul inti Node yang menyediakan server dan klien HTTP tanpa bergantung pada paket eksternal. Fungsi http.createServer mengembalikan instans http.Server yang mewarisi net.Server dan EventEmitter, sehingga siklus hidupnya dapat dipantau lewat peristiwa listening, connection, close, dan error. Setiap permintaan yang masuk memicu satu pemanggilan callback dengan dua argumen, yaitu objek IncomingMessage yang merepresentasikan permintaan dan objek ServerResponse yang dipakai untuk menulis respons. Setelah membaca topik ini, pembaca dapat membangun server HTTP minimal, memeriksa properti permintaan, serta menanggapi peristiwa listening, close, dan error dengan benar.

createServer dan callback permintaan#

Fungsi http.createServer(requestListener) membuat server baru yang menjalankan requestListener setiap kali sebuah permintaan HTTP selesai diurai dari koneksi TCP yang masuk. Argumen requestListener adalah fungsi biasa yang menerima (req, res), dipanggil secara sinkron dalam urutan kedatangan permintaan, dan tidak boleh memblokir event loop lama karena permintaan lain menunggu giliran. Pemanggilan server.listen(port, host) mengikat soket ke alamat dan port yang diberikan, dan peristiwa listening dipancarkan ketika soket siap menerima koneksi.

js server-halo.js
const http = require('node:http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('halo, dunia\n');
});

server.listen(0, '127.0.0.1', async () => {
  const { port } = server.address();
  const respons = await fetch(`http://127.0.0.1:${port}/`);
  console.log('status:', respons.status);
  console.log('header content-type:', respons.headers.get('content-type'));
  console.log('body:', (await respons.text()).trim());
  server.close();
});
text Keluaran
status: 200
header content-type: text/plain; charset=utf-8
body: halo, dunia

Pemakaian port 0 pada listen meminta sistem operasi memilih port bebas yang tersedia, sehingga beberapa server dapat dijalankan bersamaan tanpa bentrokan. Fungsi server.address() mengembalikan objek yang memuat port aktual segera setelah peristiwa listening dipancarkan. Fungsi global fetch bawaan Node digunakan di sini sebagai klien untuk melakukan permintaan ke server yang baru saja dibuat, kemudian server.close() menghentikan server agar proses keluar dengan deterministik. Pemanggilan res.end() wajib dilakukan untuk menandai akhir respons, sebab tanpa pemanggilan tersebut klien akan menunggu data yang tidak pernah datang.

Objek IncomingMessage dan ServerResponse#

Objek req pada callback adalah instans http.IncomingMessage yang dibaca sebagai stream Readable. Properti req.method berisi metode HTTP sebagai string kapital seperti GET atau POST, sedangkan req.url berisi jalur dan string kueri mentah tanpa host, misalnya /ping?halaman=2. Header permintaan diakses lewat req.headers, yaitu objek dengan nama header dalam huruf kecil, karena Node sudah menormalkan nama header menjadi lowercase sesuai konvensi HTTP.

js server-req.js
const http = require('node:http');

const server = http.createServer((req, res) => {
  const host = (req.headers.host || '').split(':')[0];
  console.log('--- permintaan masuk ---');
  console.log('method:', req.method);
  console.log('url:', req.url);
  console.log('header host:', host);
  console.log('header accept:', req.headers.accept || '(tidak ada)');
  res.writeHead(204);
  res.end();
});

server.listen(0, '127.0.0.1', async () => {
  const { port } = server.address();
  await fetch(`http://127.0.0.1:${port}/ping?halaman=2`, {
    headers: { accept: 'application/json' },
  });
  server.close();
});
text Keluaran
--- permintaan masuk ---
method: GET
url: /ping?halaman=2
header host: 127.0.0.1
header accept: application/json

Objek res adalah instans http.ServerResponse yang sekaligus merupakan stream Writable. Metode res.writeHead(statusCode, headers) menulis baris status dan header respons sekaligus, dan setelah pemanggilan ini header tidak dapat diubah lagi. Metode res.setHeader(name, value) menambah header satu per satu sebelum writeHead dipanggil, sedangkan res.end([data]) menulis data terakhir bila diberikan argumen dan menutup respons. Status 204 No Content pada contoh di atas dipakai untuk respons yang tidak memuat body, sehingga klien tidak perlu membaca data setelah header.

Diagram berikut memvisualkan siklus satu permintaan dari klien melalui callback createServer ke soket TCP, serta arah balik respons yang ditulis melalui res.end().

Klienfetch / curlhttp.createServercallback(req, res)IncomingMessage reqServerResponse resres.end()Socket TCPlistening eventpermintaanrespons
Siklus permintaan dan respons pada http.createServer

Siklus hidup server: listening, close, dan error#

Instans http.Server memancarkan beberapa peristiwa sepanjang masa pakainya. Peristiwa listening dipancarkan satu kali ketika soket siap menerima koneksi, dan server.address() baru dapat dipanggil setelah peristiwa ini. Peristiwa close dipancarkan setelah server.close() selesai menutup soket dan seluruh koneksi yang masih aktif, sehingga pemanggilan close tidak seketika menghentikan server bila ada koneksi yang masih menganggur. Peristiwa error dipancarkan ketika terjadi kegagalan, misalnya port sudah dipakai proses lain atau izin tidak cukup. Bila tidak ada penerima error yang dipasang, proses akan berakhir lewat uncaught exception, yaitu error yang tidak tertangkap penerima mana pun dan menyebabkan proses keluar.

js server-siklus.js
const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end('ok\n');
});

server.on('listening', () => {
  const addr = server.address();
  console.log('listening event: address', addr.address, 'family', addr.family);
});

server.on('close', () => {
  console.log('close event: server berhenti menerima koneksi baru');
});

server.on('error', (err) => {
  console.log('error event:', err.message);
});

server.listen(0, '127.0.0.1', async () => {
  const { port } = server.address();
  const r = await fetch(`http://127.0.0.1:${port}/`);
  console.log('respons status:', r.status);
  server.close();
});
text Keluaran
listening event: address 127.0.0.1 family IPv4
respons status: 200
close event: server berhenti menerima koneksi baru

Peristiwa connection dipancarkan ketika soket TCP baru dibuka, sebelum permintaan HTTP selesai diurai, dan membawa objek soket sebagai argumen. Pelacakan koneksi pada level ini berguna untuk menghitung koneksi aktif atau memaksa penutupan soket yang menggantung. Memasang penerima request tambahan setelah createServer(listener) menyebabkan callback terpanggil dua kali per permintaan, sebab requestListener yang diteruskan ke createServer juga dipasang ke peristiwa request secara internal.

Penerima error wajib dipasang pada server produksi karena kegagalan listen yang tidak ditangkap akan menghentikan proses tanpa pesan yang membantu. Contoh berikut memicu peristiwa error dengan mencoba listen pada port istimewa tanpa izin root, sehingga sistem operasi menolak pengikatan soket dan Node meneruskan error tersebut ke penerima error.

js server-error.js
const http = require('node:http');

const server = http.createServer((req, res) => {
  res.end('ok\n');
});

server.on('error', (err) => {
  console.log('error event:', err.code, '-', err.message);
});

server.listen(80, '127.0.0.1');
text Keluaran
error event: EACCES - listen EACCES: permission denied 127.0.0.1:80

Properti err.code pada objek Error yang dipancarkan berisi kode error libuv seperti EACCES, EADDRINUSE, atau ECONNRESET. Kode ini lebih stabil untuk dipakai sebagai dasar percabangan dibandingkan pesan err.message yang dapat berubah antar versi Node. Pada produksi, penerima error biasanya mencatat kode dan pesan, lalu menghentikan proses agar process manager dapat memulai ulang.

Kesalahan umum#

Lupa memanggil res.end() menyebabkan klien menunggu respons tanpa henti sampai timeout terjadi. Keluhan ini sering muncul ketika cabang kode tertentu kembali lebih awal tanpa menutup respons. Penggunaan pola tunggal keluar, di mana setiap cabang selalu memanggil res.end() atau mendelegasikan ke fungsi yang menjamin penutupan, menjadi cara yang paling sederhana untuk menghindarinya.

Memanggil server.listen(port) tanpa argumen host mengikat soket ke :: atau 0.0.0.0, yang berarti server menerima koneksi dari seluruh antarmuka jaringan. Pada lingkungan produksi yang dapat diakses publik, perilaku ini membuka peluang serangan bila firewall belum dikonfigurasi. Penambahan argumen host '127.0.0.1' pada pengembangan lokal menjaga server tetap hanya dapat diakses dari mesin yang sama.

Pemanggilan res.setHeader setelah res.writeHead atau setelah res.write pertama tidak berdampak karena header sudah dikirim. Bila header dinamis perlu ditetapkan berdasarkan pemrosesan permintaan, kumpulkan pada variabel sebelum writeHead dipanggil, atau pakai res.setHeader berulang sebelum tulis pertama.

Ringkasan#

  • http.createServer(listener) mengembalikan server yang memanggil listener(req, res) per permintaan, dengan req berupa IncomingMessage yang dibaca sebagai stream dan res berupa ServerResponse yang ditulis sebagai stream.
  • req.method, req.url, dan req.headers sudah siap pakai saat callback dipanggil, sedangkan res.writeHead, res.setHeader, dan res.end mengatur baris status dan header respons.
  • Peristiwa listening, close, dan error menggambarkan siklus hidup server, dan penerima error wajib dipasang agar kegagalan listen tidak menghentikan proses tanpa pesan yang membantu.
  • Port 0 pada listen meminta sistem operasi memilih port bebas, dan server.address() mengembalikan port aktual segera setelah peristiwa listening.

Referensi#

  • HTTP — Node.js — Referensi resmi modul http beserta rincian createServer, IncomingMessage, dan ServerResponse.
  • Class http.Server — Node.js — Daftar peristiwa listening, connection, close, error, dan request pada kelas http.Server.