1. Wprowadzenie
Praca z plikami i siecią to jeden z najważniejszych tematów w programowaniu. Niemal każda nowoczesna aplikacja w taki czy inny sposób współpracuje z internetem: pobiera dane, wysyła żądania, odbiera obrazy lub dokumenty. Nawet prosty program, taki jak klient czatu czy odtwarzacz muzyki, ma do czynienia z pobieraniem zasobów.
Dziś wyjaśnimy, jak to robić w Javie, na przykładzie najbardziej obrazowego i przyjemnego ładunku — obrazów. Dlaczego właśnie one?
- Widać od razu: Jeśli wszystko zadziałało, po prostu otwierasz pobrany plik i widzisz obraz. To natychmiastowy i zrozumiały rezultat.
- Uniwersalna umiejętność: Obrazy to nie tylko obrazki, ale dane binarne, „surowe bajty”. Gdy nauczysz się z nimi pracować, będziesz mógł pobierać cokolwiek: dokumenty, wideo, muzykę, archiwa. Zasada jest ta sama!
- Realne zadania: Umiejętność pobierania zasobów z sieci to fundament tworzenia każdej nowoczesnej aplikacji — od prostych pobieraczy tapet po złożone systemy współpracujące z API serwisów społecznościowych.
Zastosujemy dwa podejścia: szybka „poczta” — przez URL, oraz „profesjonalna firma transportowa” — przez HttpClient z kontrolą nagłówków, statusów i limitów czasu.
2. Najprostszy sposób: klasa URL
W Javie istnieje klasa URL, która reprezentuje adres sieciowy. Pozwala otworzyć połączenie i uzyskać strumień danych (typ InputStream). Ze strumieniem można pracować tak jak z plikiem: czytać bajty, kopiować do innego strumienia, przetwarzać jako tekst.
W istocie użycie URL to najkrótsza droga od linku w przeglądarce do pliku na dysku.
Pierwszy minimalny przykład
Załóżmy, że mamy link do obrazu:
URL url = new URL("https://example.com/image.jpg");
Files.copy(url.openStream(), Path.of("a.jpg"));
Tylko dwie linijki. Co tu się dzieje?
- Tworzymy obiekt URL, przekazując mu łańcuch z adresem obrazu.
- Wywołujemy metodę openStream(), która otwiera połączenie sieciowe i zwraca strumień InputStream.
- Za pomocą metody Files.copy kopiujemy zawartość strumienia do pliku "a.jpg".
Efekt: obraz z internetu zostaje zapisany w naszym katalogu roboczym.
Wariant z transferTo
Jest jeszcze jeden sposób przekazania danych ze strumienia do pliku:
InputStream in = new URL("https://example.com/image.jpg").openStream();
in.transferTo(Files.newOutputStream(Path.of("b.jpg")));
Tutaj mówimy wprost: weź strumień in i „przepompuj” wszystkie dane metodą transferTo do strumienia, który zapisuje plik "b.jpg".
Rezultat będzie taki sam: obraz trafi na dysk.
Ważne szczegóły
Takie podejście jest bardzo proste, ale ma niuanse. Po pierwsze, w ogóle nie sprawdzamy, co dokładnie przyszło pod danym linkiem. Może to być obraz. A może — strona HTML z błędem 404. W obu przypadkach plik zostanie zapisany. Różnica wyjdzie na jaw dopiero później, gdy spróbujesz otworzyć go w przeglądarce obrazów.
3. Nowoczesny sposób: HttpClient
Począwszy od Javy 11 do biblioteki standardowej trafiło nowe narzędzie — HttpClient. Umożliwia wykonywanie żądań HTTP na nowoczesnym poziomie: pracę z metodami GET, POST i innymi, zarządzanie limitami czasu, kontrolę nagłówków i statusów, obsługę przekierowań.
Do pobierania obrazów jest to szczególnie przydatne: możemy upewnić się, że plik faktycznie został pobrany pomyślnie, a nie że dostaliśmy błąd.
Minimalny przykład z HttpClient
URI uri = URI.create("https://example.com/image.jpg"); // URI — bardziej nowoczesna wersja URL
HttpClient client = HttpClient.newHttpClient(); // Tworzymy obiekt HttpClient
HttpRequest request = HttpRequest.newBuilder(uri).build(); // Tworzymy obiekt "żądanie"
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
Files.write(Path.of("c.jpg"), response.body());
Teraz krok po kroku:
- Tworzymy klienta HttpClient.
- Konstruujemy żądanie HttpRequest pod wskazany adres.
- Wysyłamy żądanie i otrzymujemy odpowiedź jako tablicę bajtów: HttpResponse.BodyHandlers.ofByteArray().
- Zapisujemy te bajty do pliku metodą Files.write — "c.jpg".
Rezultat — ten sam obraz, ale teraz mamy więcej informacji o tym, jak dokładnie odpowiedział serwer.
Sprawdzanie statusu odpowiedzi
Obiekt response ma metodę statusCode(). Dzięki niej możemy upewnić się, że serwer zwrócił pomyślną odpowiedź:
if (response.statusCode() == 200)
{
Files.write(Path.of("ok.jpg"), response.body());
}
else
{
System.out.println("Błąd: kod " + response.statusCode());
}
Teraz dokładnie wiemy, że zapisaliśmy to, czego oczekiwaliśmy, a nie stronę z błędem.
Pobieranie nagłówków
Załóżmy, że chcemy sprawdzić typ zawartości:
String type = response.headers().firstValue("Content-Type").orElse("nieznany");
System.out.println("Typ zawartości: " + type);
Jeśli serwer zwróci "image/png" lub "image/jpeg", to rzeczywiście jest obraz. Jeśli jednak przyszło "text/html", to sygnał alarmowy.
Limity czasu
Aby program się nie zawiesił, gdy serwer długo odpowiada, można ustawić ograniczenie:
URI uri = URI.create("https://example.com/image.jpg");
HttpRequest req = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(5))
.build();
Jeśli serwer nie odpowie w ciągu 5 sekund, żądanie zakończy się błędem.
Obsługa przekierowań
Czasem linki nie prowadzą bezpośrednio do pliku, lecz najpierw do strony przekierowującej. Automatyczne podążanie za przekierowaniami można włączyć tak:
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
4. Scenariusze praktyczne
Pobieranie wielu obrazów po kolei
Często trzeba pobrać nie jeden, lecz dziesiątki albo setki obrazów. Na przykład piszesz program, który zapisuje awatary użytkowników lub zdjęcia produktów. Z HttpClient robi się to pętlą:
var client = HttpClient.newHttpClient();
String[] urls = {
"https://example.com/img1.jpg",
"https://example.com/img2.jpg"
};
for (int i = 0; i < urls.length; i++)
{
var uri = URI.create(urls[i]);
var request = HttpRequest.newBuilder(uri).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() == 200) {
Files.write(Path.of("img" + i + ".jpg"), response.body());
}
}
Sprawdzanie rozmiaru pliku
Bywa przydatne wiedzieć, ile bajtów ma obraz. Serwer może to podać w nagłówku "Content-Length":
String length = response.headers().firstValue("Content-Length").orElse("?");
System.out.println("Rozmiar: " + length + " bajtów");
Jeśli nagłówka nie ma, zawsze można po prostu wziąć response.body().length.
Pobranie i wyświetlenie obrazu w programie
Czasem obraz trzeba nie tylko pobrać, ale i od razu wyświetlić. Do tego można użyć biblioteki javax.imageio.ImageIO:
InputStream in = new URL("https://example.com/pic.png").openStream();
BufferedImage img = ImageIO.read(in);
System.out.println("Szerokość: " + img.getWidth() + ", wysokość: " + img.getHeight());
W ten sposób możemy sprawdzić obraz od razu w pamięci, nawet nie zapisując go na dysk.
GO TO FULL VERSION