Przejdź do treści

Pobieranie danych z API przez HttpClient

  • WWW i API
  • poziom: średni
  • aktualizacja: 15 sierpnia 2026

Od Javy 11 nie potrzebujesz Apache HttpClient ani OkHttp — klasa java.net.http.HttpClient z biblioteki standardowej wystarcza do zwykłej integracji z API. Największa pułapka: metoda send() nie rzuca wyjątku, gdy serwer odpowie kodem 404 albo 500 — dostajesz zwyczajną odpowiedź i musisz sam sprawdzić statusCode(). Druga to brak limitów czasu: klient bez connectTimeout() i timeout() potrafi czekać w nieskończoność.

JAVA KlientApi.java 145 linii Pobierz .java
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Klient REST API oparty na java.net.http.HttpClient — skryptyJAVA.pl
 * Zapisz jako: KlientApi.java
 */
public class KlientApi {

    private static final String PRZEGLADARKA = "skryptyJAVA-KlientApi/1.0";
    private static final Duration LIMIT_ZADANIA = Duration.ofSeconds(15);

    /** Klienta buduj RAZ — trzyma pulę połączeń, więc nie twórz go przy każdym żądaniu. */
    private static final HttpClient KLIENT = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(10))       // limit na nawiązanie połączenia
            .followRedirects(HttpClient.Redirect.NORMAL)  // podążaj za 301/302, ale nie z HTTPS na HTTP
            .build();

    /** Błąd zgłaszany, gdy serwer odpowie kodem spoza zakresu 2xx. Pole kod to np. 404 albo 500. */
    public static class BladApi extends RuntimeException {
        private static final long serialVersionUID = 1L;
        public final int kod;
        public BladApi(int kod, String tresc) {
            super("HTTP " + kod + " — " + (tresc.length() > 200 ? tresc.substring(0, 200) + "..." : tresc));
            this.kod = kod;
        }
    }

    /**
     * Wysyła żądanie GET i zwraca ciało odpowiedzi jako tekst.
     *
     * @param adres  pełny adres URL zasobu
     * @throws BladApi gdy kod odpowiedzi nie mieści się w zakresie 2xx, np. 404 albo 500
     */
    public static String pobierz(String adres) throws IOException, InterruptedException {
        return sprawdz(KLIENT.send(szkielet(adres).GET().build(), HttpResponse.BodyHandlers.ofString()));
    }

    /** Wysyła POST z gotowym dokumentem JSON i zwraca odpowiedź serwera jako tekst. */
    public static String wyslijJson(String adres, String json) throws IOException, InterruptedException {
        HttpRequest zadanie = szkielet(adres)
                .header("Content-Type", "application/json; charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
                .build();
        return sprawdz(KLIENT.send(zadanie, HttpResponse.BodyHandlers.ofString()));
    }

    /** Pobiera zasób strumieniowo i podmienia cel dopiero po poprawnej odpowiedzi 2xx. */
    public static Path pobierzPlik(String adres, Path cel) throws IOException, InterruptedException {
        Path wynik = cel.toAbsolutePath().normalize();
        Path katalog = wynik.getParent();
        if (katalog == null) { throw new IOException("Plik docelowy musi mieć katalog nadrzędny."); }
        Files.createDirectories(katalog);
        HttpRequest zadanie = szkielet(adres).timeout(Duration.ofMinutes(5)).GET().build();
        HttpResponse<InputStream> odpowiedz = KLIENT.send(
                zadanie, HttpResponse.BodyHandlers.ofInputStream());
        try (InputStream body = odpowiedz.body()) {
            if (odpowiedz.statusCode() < 200 || odpowiedz.statusCode() > 299) {
                String tresc = new String(body.readNBytes(2048), StandardCharsets.UTF_8);
                throw new BladApi(odpowiedz.statusCode(), tresc);
            }
            Path tymczasowy = Files.createTempFile(katalog, "pobieranie-", ".part");
            try {
                Files.copy(body, tymczasowy, StandardCopyOption.REPLACE_EXISTING);
                Files.move(tymczasowy, wynik, StandardCopyOption.REPLACE_EXISTING);
            } finally {
                Files.deleteIfExists(tymczasowy);
            }
        }
        return wynik;
    }

    /** Asynchroniczne GET — nie blokuje wątku, wynik odbierasz przez CompletableFuture. */
    public static CompletableFuture<String> pobierzAsync(String adres) {
        return KLIENT.sendAsync(szkielet(adres).GET().build(), HttpResponse.BodyHandlers.ofString())
                .thenApply(KlientApi::sprawdz);
    }

    /** Wyciąga wartość prostego pola z płaskiego JSON-a bez biblioteki. Zwraca null, gdy pola nie ma. */
    public static String pole(String json, String nazwa) {
        String wzorzec = "\"" + Pattern.quote(nazwa)
                + "\"\\s*:\\s*(?:\"((?:\\\\.|[^\"\\\\])*)\"|([-\\d.eE+]+|true|false|null))";
        Matcher dopasowanie = Pattern.compile(wzorzec).matcher(json);
        if (!dopasowanie.find()) { return null; }
        String tekst = dopasowanie.group(1);
        return tekst != null ? tekst.replace("\\\"", "\"").replace("\\\\", "\\") : dopasowanie.group(2);
    }

    /** Wspólny szkielet żądania: nagłówki i limit odpowiedzi, którego zakres zależy od BodyHandler. */
    private static HttpRequest.Builder szkielet(String adres) {
        return HttpRequest.newBuilder(URI.create(adres))
                .header("Accept", "application/json")
                .header("User-Agent", PRZEGLADARKA)
                .timeout(LIMIT_ZADANIA);
    }

    /** Przepuszcza odpowiedzi 2xx, resztę zamienia na czytelny wyjątek BladApi. */
    private static String sprawdz(HttpResponse<String> odpowiedz) {
        int kod = odpowiedz.statusCode();
        if (kod < 200 || kod > 299) { throw new BladApi(kod, odpowiedz.body()); }
        return odpowiedz.body();
    }

    /** Wykonuje krok demonstracji; przy błędzie sieci wypisuje komunikat zamiast przerywać program. */
    private static void demo(String etap, Callable<String> krok) {
        try {
            System.out.println(etap + ": " + krok.call());
        } catch (Exception e) {
            if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); }
            System.out.println("[" + etap + "] nie powiodło się: "
                    + (e.getMessage() != null ? e.getMessage() : e.getClass().getName()));
        }
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    public static void main(String[] args) {
        System.out.println("pole: " + pole("{\"login\":\"duke\",\"id\":42}", "login"));  // duke, bez sieci

        demo("GET", () -> {
            String json = pobierz("https://api.github.com/repos/openjdk/jdk");
            return pole(json, "full_name") + ", gwiazdek: " + pole(json, "stargazers_count");
        });
        demo("POST", () -> pole(wyslijJson("https://httpbin.org/post", "{\"jezyk\":\"java\"}"), "data"));
        demo("PLIK", () -> {
            Path cel = Files.createTempDirectory("klient-api").resolve("zen.txt");
            return pobierzPlik("https://api.github.com/zen", cel) + ", bajtow: " + Files.size(cel);
        });
        demo("ASYNC", () -> pobierzAsync("https://api.github.com/zen").get(20, TimeUnit.SECONDS));
    }
}

Jak użyć tego skryptu

  1. Skopiuj kod i zapisz go w pliku KlientApi.java — nazwa pliku odpowiada nazwie klasy publicznej — a potem uruchom go poleceniem java KlientApi.java. Program odpyta publiczne API GitHuba oraz serwis httpbin.org i wypisze wyniki czterech przykładów; bez dostępu do internetu każdy z nich zgłosi tylko komunikat o błędzie, a program i tak zakończy się normalnie. Wymagana jest Java 11 lub nowsza — wcześniejsze wersje nie mają pakietu java.net.http.
  2. W swoim projekcie wywołuj gotowe metody: KlientApi.pobierz(adres) dla GET, KlientApi.wyslijJson(adres, json) dla POST i KlientApi.pobierzPlik(adres, sciezka), gdy zapisujesz odpowiedź na dysk. Wszystkie korzystają ze wspólnej stałej KLIENT — HttpClient jest bezpieczny wątkowo i trzyma pulę połączeń, więc jedna instancja obsłuży całą aplikację.
  3. Gdy potrzebujesz uwierzytelnienia, dopisz nagłówek w metodzie szkielet(...) — np. .header("Authorization", "Bearer " + token). Trafi on wtedy do wszystkich żądań naraz.
  4. Do wielu równoległych zapytań użyj pobierzAsync(...) i połącz wyniki przez CompletableFuture.allOf(...). Pamiętaj, żeby na końcu odebrać wynik metodą get() z limitem czasu albo join() — inaczej program zakończy się, zanim odpowiedzi dotrą.

Warto wiedzieć

  • Metoda pole(...) to prowizorka na regularnych wyrażeniach — poradzi sobie tylko z płaskim JSON-em i pierwszym pasującym polem. Do prawdziwego parsowania użyj biblioteki Jackson albo Gson; próba obsłużenia zagnieżdżonych obiektów i tablic wyrażeniem regularnym zawsze kończy się błędami.
  • Kody 4xx i 5xx nie są dla HttpClient błędem — dostajesz zwykłą odpowiedź, dlatego każda przechodzi tu przez kontrolę statusu. pobierzPlik(...) czyta z odpowiedzi błędu najwyżej 2 KiB, a udane dane zapisuje do unikalnego pliku tymczasowego i dopiero po pełnym pobraniu podmienia cel. Dzięki temu krótka odpowiedź nie zostawia końcówki starego pliku, a błąd HTTP nie niszczy jego poprzedniej zawartości.
  • Ustawiaj oba limity czasu: connectTimeout() dotyczy tylko nawiązania połączenia, a timeout() pilnuje otrzymania odpowiedzi. Przy BodyHandlers.ofString() obejmuje także pobranie tekstu, lecz przy ofInputStream() kończy się po nagłówkach — kolejne read() mogą wisieć. Dla dużych plików dołóż strażnika zastoju, który zamknie strumień, gdy licznik bajtów zbyt długo nie rośnie.
  • Klucza API nigdy nie wpisuj na sztywno w kodzie ani nie doklejaj do adresu URL — adresy lądują w logach serwera i w historii przeglądarki, a klucz w kodzie pojedzie razem z nim do repozytorium. Czytaj go ze zmiennej środowiskowej i przekazuj w nagłówku.
#http #httpclient #api #rest #parsowanie

Podobne skrypty