Chapter sebelumnya memperkenalkan mesin HttpClient; sekarang kita isi bahan bakarnya. Sebuah permintaan HTTP berdiri dari empat unsur: method, URI, header, dan body. API HttpRequest.newBuilder() membangun keempatnya secara bertahap dengan objek final yang tidak bisa diubah, sementara BodyPublishers mengubah data menjadi aliran kiriman. Di sisi seberang, HttpResponse membungkus jawaban server — status code, header, dan body — dengan bentuk body yang kita pilih lewat BodyHandlers. Chapter ini melatih keempat method HTTP utama, header khusus, serta cara membaca status dan header respons dengan benar.

Membangun Permintaan dengan Method Berbeda #

Method HTTP menyatakan maksud permintaan: GET mengambil data, POST mengirim data baru, PUT memperbarui, dan DELETE menghapus. Pada builder, GET() dan DELETE() tidak memakai body, sedangkan POST(...) dan PUT(...) wajib diberi BodyPublishers — mesin penerbit body yang paling sederhananya BodyPublishers.ofString(...). Header khusus ditambahkan dengan header(nama, nilai) dan boleh dipanggil berulang.

Program berikut mengutus keempat method ke satu server lokal yang sama. Server membaca method, body, dan header X-Kategori dari permintaan, lalu merangkum semuanya dalam balasan agar kita bisa memastikan setiap unsur benar-benar tiba:

import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class KirimBerbagai {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/tugas", exchange -> {
            String metode = exchange.getRequestMethod();
            String bodyMasuk = new String(exchange.getRequestBody().readAllBytes(),
                    StandardCharsets.UTF_8);
            String headerSapa = exchange.getRequestHeaders().getFirst("X-Kategori");

            String jawaban = metode + " diterima";
            if (!bodyMasuk.isEmpty()) {
                jawaban += ", body: " + bodyMasuk;
            }
            if (headerSapa != null) {
                jawaban += ", header X-Kategori=" + headerSapa;
            }
            byte[] body = jawaban.getBytes(StandardCharsets.UTF_8);
            exchange.sendResponseHeaders(200, body.length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(body);
            }
        });
        server.start();

        String dasar = "http://127.0.0.1:" + server.getAddress().getPort() + "/tugas";
        HttpClient klien = HttpClient.newHttpClient();
        try {
            HttpRequest get = HttpRequest.newBuilder(URI.create(dasar)).GET().build();
            HttpRequest post = HttpRequest.newBuilder(URI.create(dasar))
                    .POST(HttpRequest.BodyPublishers.ofString("tugas baru Budi"))
                    .build();
            HttpRequest put = HttpRequest.newBuilder(URI.create(dasar))
                    .header("X-Kategori", "penting")
                    .PUT(HttpRequest.BodyPublishers.ofString("tugas revisi", StandardCharsets.UTF_8))
                    .build();
            HttpRequest hapus = HttpRequest.newBuilder(URI.create(dasar)).DELETE().build();

            for (HttpRequest req : new HttpRequest[]{get, post, put, hapus}) {
                HttpResponse<String> res = klien.send(req,
                        HttpResponse.BodyHandlers.ofString());
                System.out.printf("%-6s -> %d : %s%n", req.method(), res.statusCode(), res.body());
            }
        } finally {
            server.stop(0);
        }
    }
}

Output yang Dihasilkan:

text
GET    -> 200 : GET diterima
POST   -> 200 : POST diterima, body: tugas baru Budi
PUT    -> 200 : PUT diterima, body: tugas revisi, header X-Kategori=penting
DELETE -> 200 : DELETE diterima

Mari kita telaah bagian-bagian pentingnya:

  1. Baris 13-14 (exchange.getRequestMethod() dan getRequestBody()): sisi server mengidentifikasi method dari HttpExchange, lalu membaca seluruh body masuk dari getRequestBody(). Method readAllBytes() pada stream menghasilkan byte mentah yang kita terjemahkan ke teks dengan StandardCharsets.UTF_8.
  2. Baris 16 (getFirst("X-Kategori")): header permintaan dibaca dari peta getRequestHeaders(); getFirst mengambil satu nilai pertama untuk nama header tersebut, dan mengembalikan null bila header tidak dikirim.
  3. Baris 33-36 (builder get, post, put, hapus): keempat objek HttpRequest dibangun dari URI yang sama dengan perlakuan berbeda. Perhatikan bahwa PUT menggabungkan header(...) dan BodyPublishers.ofString(...) — builder boleh menerima keduanya berurutan apa pun urutannya.
  4. Baris 44-46 (req.method()): objek permintaan menyimpan methodnya sendiri, jadi kita bisa mencetak label tanpa memelihara variabel terpisah.
  5. Baris 44-47 (klien.send(req, BodyHandlers.ofString())): satu client mengirim empat permintaan berbeda bergilir. Setiap send() sinkron mengembalikan HttpResponse<String> lengkap sebelum baris berikutnya berjalan, sehingga urutan output selalu sama.

Membaca Status, Header, dan Body #

Respons HTTP bukan sekadar “berhasil atau gagal”. Status code membawa makna: 2xx sukses, 3xx redirect, 4xx kesalahan pemanggil (client), dan 5xx kesalahan server. Yang penting dipahami: HttpClient tidak melempar exception untuk status 404 atau 500 — respons tersebut adalah jawaban sah yang ditangkap lewat statusCode(), dan keputusan menanggapi kegagalan sepenuhnya milik kode aplikasi. Header respons dibaca lewat headers(), misalnya firstValue("location") yang berbentuk Optional<String>.

Program berikut menampilkan tiga wajah respons dari satu server: 201 Created lengkap dengan header Location, 405 Method Not Allowed, dan 404 Not Found untuk path yang tidak punya handler:

import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class LaporanStatus {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/daftar", exchange -> {
            if (exchange.getRequestMethod().equals("POST")) {
                byte[] body = "{\"id\":42}".getBytes(StandardCharsets.UTF_8);
                exchange.getResponseHeaders().set("Location", "/daftar/42");
                exchange.sendResponseHeaders(201, body.length);
                try (OutputStream os = exchange.getResponseBody()) {
                    os.write(body);
                }
            } else {
                exchange.sendResponseHeaders(405, -1);
                exchange.close();
            }
        });
        server.start();

        String dasar = "http://127.0.0.1:" + server.getAddress().getPort();
        HttpClient klien = HttpClient.newHttpClient();
        try {
            HttpResponse<String> buat = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/daftar"))
                            .POST(HttpRequest.BodyPublishers.ofString("nama=Budi")).build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("POST /daftar : " + buat.statusCode()
                    + " sukses=" + (buat.statusCode() / 100 == 2)
                    + " location=" + buat.headers().firstValue("location").orElse("-")
                    + " body=" + buat.body());

            HttpResponse<String> ambil = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/daftar")).GET().build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("GET  /daftar : " + ambil.statusCode()
                    + " sukses=" + (ambil.statusCode() / 100 == 2));

            HttpResponse<String> hilang = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/hilang")).GET().build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("GET  /hilang : " + hilang.statusCode()
                    + " sukses=" + (hilang.statusCode() / 100 == 2));
        } finally {
            server.stop(0);
        }
    }
}

Output yang Dihasilkan:

text
POST /daftar : 201 sukses=true location=/daftar/42 body={"id":42}
GET  /daftar : 405 sukses=false
GET  /hilang : 404 sukses=false

Tiga detail patut dicermati dari hasilnya. Pertama, 201 dari POST disertai header Location yang menunjuk sumber daya baru — pola standar REST untuk pembuatan data, dan firstValue(...).orElse("-") memberi nilai cadangan bila header absen. Kedua, pembagian statusCode() / 100 == 2 adalah cara ringkas menguji keluarga sukses; hasilnya true untuk 200 dan 201, false untuk 405 dan 404. Ketiga, 405 dan 404 sama-sama sampai sebagai objek HttpResponse normal — tidak ada exception yang terjadi pada ketiga panggilan send().

BodyPublishers dan BodyHandlers #

Kedua keluarga factory ini menentukan bentuk data di kedua arah. BodyPublishers mengubah data kita menjadi aliran kiriman: ofString untuk teks (dengan charset opsional), ofByteArray untuk byte mentah, ofFile(Path) untuk mengirim isi berkas tanpa memuat seluruhnya ke memori, ofInputStream untuk data yang diproduksi bertahap, dan noBody() untuk permintaan tanpa body. BodyHandlers bekerja pada arah sebaliknya: ofString() mengumpulkan seluruh respons menjadi String, ofByteArray() menjadi array byte, ofFile(Path) menuliskan body langsung ke berkas (praktis untuk mengunduh berkas besar), ofInputStream() menyerahkan aliran agar kode kita memproses bertahap, dan discarding() membuang body sama sekali saat hanya status yang berarti.

Dengan penguasaan method, header, dan body, kita sudah bisa bercakap HTTP secara penuh. Format data yang paling umum dilintasi lewat jalur ini adalah JSON — dan pada Chapter berikutnya kita rakit serta urai JSON dengan tangan sendiri sebelum melihat pustaka produksi.

Referensi #

Berkas Sumber

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

KirimBerbagai.java

Unduh berkas
java
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class KirimBerbagai {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/tugas", exchange -> {
            String metode = exchange.getRequestMethod();
            String bodyMasuk = new String(exchange.getRequestBody().readAllBytes(),
                    StandardCharsets.UTF_8);
            String headerSapa = exchange.getRequestHeaders().getFirst("X-Kategori");

            String jawaban = metode + " diterima";
            if (!bodyMasuk.isEmpty()) {
                jawaban += ", body: " + bodyMasuk;
            }
            if (headerSapa != null) {
                jawaban += ", header X-Kategori=" + headerSapa;
            }
            byte[] body = jawaban.getBytes(StandardCharsets.UTF_8);
            exchange.sendResponseHeaders(200, body.length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(body);
            }
        });
        server.start();

        String dasar = "http://127.0.0.1:" + server.getAddress().getPort() + "/tugas";
        HttpClient klien = HttpClient.newHttpClient();
        try {
            HttpRequest get = HttpRequest.newBuilder(URI.create(dasar)).GET().build();
            HttpRequest post = HttpRequest.newBuilder(URI.create(dasar))
                    .POST(HttpRequest.BodyPublishers.ofString("tugas baru Budi"))
                    .build();
            HttpRequest put = HttpRequest.newBuilder(URI.create(dasar))
                    .header("X-Kategori", "penting")
                    .PUT(HttpRequest.BodyPublishers.ofString("tugas revisi", StandardCharsets.UTF_8))
                    .build();
            HttpRequest hapus = HttpRequest.newBuilder(URI.create(dasar)).DELETE().build();

            for (HttpRequest req : new HttpRequest[]{get, post, put, hapus}) {
                HttpResponse<String> res = klien.send(req,
                        HttpResponse.BodyHandlers.ofString());
                System.out.printf("%-6s -> %d : %s%n", req.method(), res.statusCode(), res.body());
            }
        } finally {
            server.stop(0);
        }
    }
}

LaporanStatus.java

Unduh berkas
java
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class LaporanStatus {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/daftar", exchange -> {
            if (exchange.getRequestMethod().equals("POST")) {
                byte[] body = "{\"id\":42}".getBytes(StandardCharsets.UTF_8);
                exchange.getResponseHeaders().set("Location", "/daftar/42");
                exchange.sendResponseHeaders(201, body.length);
                try (OutputStream os = exchange.getResponseBody()) {
                    os.write(body);
                }
            } else {
                exchange.sendResponseHeaders(405, -1);
                exchange.close();
            }
        });
        server.start();

        String dasar = "http://127.0.0.1:" + server.getAddress().getPort();
        HttpClient klien = HttpClient.newHttpClient();
        try {
            HttpResponse<String> buat = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/daftar"))
                            .POST(HttpRequest.BodyPublishers.ofString("nama=Budi")).build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("POST /daftar : " + buat.statusCode()
                    + " sukses=" + (buat.statusCode() / 100 == 2)
                    + " location=" + buat.headers().firstValue("location").orElse("-")
                    + " body=" + buat.body());

            HttpResponse<String> ambil = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/daftar")).GET().build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("GET  /daftar : " + ambil.statusCode()
                    + " sukses=" + (ambil.statusCode() / 100 == 2));

            HttpResponse<String> hilang = klien.send(
                    HttpRequest.newBuilder(URI.create(dasar + "/hilang")).GET().build(),
                    HttpResponse.BodyHandlers.ofString());
            System.out.println("GET  /hilang : " + hilang.statusCode()
                    + " sukses=" + (hilang.statusCode() / 100 == 2));
        } finally {
            server.stop(0);
        }
    }
}