Skrócenie URL-a w Javie to jedno żądanie POST przez java.net.http.HttpClient. Zbuduj żądanie, ustaw klucz API jako nagłówek Bearer, wyślij je i odczytaj short_url z treści odpowiedzi. Klient jest dostępny w JDK od Javy 11, więc pierwsza działająca wersja nie wymaga żadnych zależności.
Większość wyników wyszukiwania dotyczących Javy mówi o tym, jak "zbudować skracacz URL z użyciem Spring Boot i JPA", ale to problem przechowywania danych, a nie ten tutaj. Jeśli właśnie tego potrzebujesz, jak zbudować skracacz URL omawia projekt; jeśli chcesz uzyskać krótki link z istniejącej usługi, zacznij od przeglądu bezpłatnego API skracacza URL, aby poznać kształt endpointu i uwierzytelnianie.
Ten sam przewodnik dla innych środowisk uruchomieniowych: Python, JavaScript, Go i Ruby.
Najszybszy sposób: jedno żądanie przez współdzielonego klienta
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public final class Shortener {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
public static void main(String[] args) throws Exception {
String body = """
{"destination_url":"https://example.com/spring-sale?utm_source=newsletter"}""";
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException("shorten failed: " + response.statusCode());
}
System.out.println(response.body()); // {"id":"...","short_url":"https://s.elido.me/ab12cd"}
}
}
Dwa limity czasu pełnią różne funkcje. connectTimeout ustawiony przy budowaniu klienta ogranicza czas, jaki może zająć uzgadnianie TCP i TLS; timeout ustawiony dla żądania ogranicza całe wymienianie danych. Ustawienie tylko pierwszego sprawia, że serwer, który zaakceptuje połączenie, a potem przestanie odpowiadać, będzie bezterminowo zajmował wątek.
Sprawdzenie statusCode() >= 400 nie jest defensywnym programowaniem, tylko częścią kontraktu. send zgłasza IOException, gdy gniazdo przestanie działać, oraz HttpTimeoutException, gdy upłynie termin, ale nigdy z powodu błędu HTTP. Unieważniony klucz daje zupełnie zwyczajny obiekt odpowiedzi z błędem JSON w treści.
W prawdziwym kodzie sparsuj treść za pomocą Jacksona. Text block jest uczciwym rozwiązaniem dla jednego zakodowanego na stałe pola, ale przestaje nim być w chwili, gdy docelowy URL zawiera cudzysłów.
Używaj jednego klienta ponownie, a nie twórz go dla każdego wywołania
HttpClient jest bezpieczny wątkowo i niezmienny po zbudowaniu, a każda instancja posiada pulę połączeń oraz executor. Budowanie klienta wewnątrz metody pomocniczej oznacza, że każde wywołanie ponownie wykonuje uzgadnianie TLS i pozostawia executor, którym później musi zająć się garbage collector. Jedna statyczna instancja na aplikację - to cała zasada.
Ponowienia dla 429 i 5xx bez tworzenia duplikatów
Jest jeden błąd, który warto uwzględnić w projekcie. Żądanie POST dociera, link zostaje utworzony, ale odpowiedź ginie po drodze. Twój kod widzi przekroczenie limitu czasu, ponawia żądanie, a teraz dwa krótkie linki wskazują to samo miejsce docelowe.
Idempotency-Key rozwiązuje ten problem, a wyprowadzenie tego klucza z miejsca docelowego zamiast z losowego UUID oznacza, że ponowne uruchomienie tej samej partii nic nie kosztuje i nie tworzy duplikatów.
static String shorten(String destination, int attempts) throws Exception {
String key = HexFormat.of().formatHex(
MessageDigest.getInstance("SHA-256").digest(destination.getBytes(UTF_8)));
for (int attempt = 0; attempt < attempts; attempt++) {
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://api.elido.app/v1/links"))
.header("Authorization", "Bearer " + System.getenv("ELIDO_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(
Map.of("destination_url", destination))))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 400) {
return MAPPER.readTree(response.body()).get("short_url").asText();
}
if (status == 429) {
Thread.sleep(Duration.ofSeconds(response.headers()
.firstValue("Retry-After").map(Long::parseLong).orElse(2L)));
} else if (status >= 500) {
Thread.sleep(Duration.ofSeconds(1L << attempt)); // 1s, 2s, 4s
} else {
throw new IllegalStateException("shorten failed: " + status + " " + response.body());
}
}
throw new IllegalStateException("shorten failed after " + attempts + " attempts");
}
401 lub 422 kończy działanie przy pierwszym przejściu, zamiast zużywać trzy próby na coś, co nigdy się nie zmieni. Warto czekać tylko na 429 i 5xx, a gałąź 429 respektuje liczbę podaną przez serwer zamiast zgadywać. Uzasadnienie tych decyzji znajdziesz w szczegółowym omówieniu limitów szybkości i idempotencji.
Chcesz uruchomić to na działającym endpoincie? Utwórz klucz w bezpłatnym planie, wyeksportuj ELIDO_API_KEY, a powyższy kod skompiluje się i uruchomi bez zmian w Javie 21.
Masowo: wirtualne wątki z limitem
Wirtualne wątki zostały na dobre wprowadzone w Javie 21 i zmieniają charakter tego problemu. Blokujące operacje wejścia-wyjścia na wirtualnym wątku kosztują prawie nic, więc jeden wątek na URL nie jest już lekkomyślnością. Limit szybkości jednak się nie zmienił, dlatego Semaphore ma większe znaczenie niż executor.
static Map<String, String> shortenAll(List<String> urls) throws Exception {
Map<String, String> out = new ConcurrentHashMap<>();
Semaphore gate = new Semaphore(8); // 8 requests in flight, whatever urls.size() is
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String url : urls) {
executor.submit(() -> {
gate.acquire();
try {
out.put(url, shorten(url, 3));
} catch (Exception e) {
out.put(url, "ERROR: " + e.getMessage()); // one bad URL, not a dead batch
} finally {
gate.release();
}
return null;
});
}
} // close() waits for every task to finish
return out;
}
Blok try-with-resources wykonuje prawdziwą pracę: zamknięcie executora wirtualnych wątków czeka na zakończenie wszystkich przesłanych zadań, więc nie trzeba urządzać tańca z awaitTermination. ConcurrentHashMap z kluczem w postaci oryginalnego URL-a utrzymuje widoczność częściowych błędów i pozwala powtarzać uruchomienie.
W Javie od 11 do 17 ten sam schemat działa z sendAsync i stałą pulą wątków. W żadnej wersji nie działa natomiast CompletableFuture.allOf dla nieograniczonej listy: uruchamia wszystko naraz i zbiera stertę odpowiedzi 429.
Kiedy wygenerowany klient zasługuje na swoje miejsce
Dla jednego endpointu powyższy kod jest całą integracją i zależność niczego nie wnosi. Sytuacja zmienia się, gdy wyświetlasz listę linków z paginacją, filtrujesz po tagu i mapujesz pół tuzina kształtów odpowiedzi: wygenerowane modele oraz klient, który zna już reguły ponowień, zwracają się same. Strona API i SDK zawiera aktualną listę, a szybki start SDK pokazuje typowaną wersję tego samego wywołania.
Tak czy inaczej zasady są te same: jeden współdzielony klient, termin dla każdego żądania, odczytany kod statusu przed treścią oraz stabilny klucz idempotencji dla wszystkiego, co może zostać uruchomione dwukrotnie. Gdy linki są tworzone przez usługę, a nie z laptopa, webhooki dla zdarzeń linków są lepsze od odpytywania, jeśli chcesz dowiedzieć się, co wydarzyło się później.
Przeczytaj serię artykułów głównych
Ten artykuł należy do klastra engineering. Zacznij od przewodnika po bezpłatnym API skracacza URL, a następnie przeczytaj limity szybkości i idempotencję. Aktualnym źródłem referencyjnym jest dokumentacja API, a rozwiązania dla deweloperów obejmują pozostałe elementy oferty.
Powiązane na blogu
Najczęściej zadawane pytania
Jak skrócić URL w Javie?
Zbuduj żądanie POST za pomocą HttpRequest, wyślij je przez współdzielony HttpClient i odczytaj short_url z treści odpowiedzi. java.net.http jest częścią JDK od Javy 11, więc działająca wersja nie potrzebuje żadnej zależności Mavena: ustaw nagłówek Authorization, wyślij niewielkie ciało JSON i przed parsowaniem sprawdź statusCode().
Czy do wywoływania REST API w Javie potrzebuję OkHttp lub Apache HttpClient?
Nie w tym przypadku. Wbudowany java.net.http.HttpClient obsługuje żądania POST JSON, limity czasu i wysyłanie asynchroniczne. Klient zewnętrzny ma sens, gdy potrzebujesz interceptorów, metryk połączeń lub obsługi HTTP/2 push, których klient JDK nie udostępnia, ale pojedyncze wywołanie create ich nie wymaga.
Czy HttpClient Javy zgłasza wyjątek dla 404 lub 500?
Nie. Zgłasza IOException wyłącznie przy awarii transportu oraz HttpTimeoutException, gdy upłynie limit czasu żądania. Odpowiedź 4xx lub 5xx wraca jako zwykły HttpResponse, więc statusCode() musisz odczytać samodzielnie. Pominięcie tego sprawdzenia jest zwykle powodem, dla którego unieważniony klucz wygląda jak udane wywołanie.
Czy powinienem tworzyć nowy HttpClient dla każdego żądania?
Nie. Każdy HttpClient ma własną pulę połączeń i executor, więc budowanie jednego na żądanie pozbywa się ponownego użycia połączeń i może nagromadzić wątki. Utwórz jedną statyczną instancję dla aplikacji i współdziel ją; klasa jest udokumentowana jako bezpieczna wątkowo i przeznaczona do ponownego użycia.
Jak skrócić wiele URL-i jednocześnie w Javie?
Uruchom wywołania na executorze wirtualnych wątków z Semaphore ograniczającym liczbę żądań w toku, zwykle do około ośmiu. Wirtualne wątki sprawiają, że jeden wątek na URL jest wystarczająco tani, ale limit szybkości API się nie zmienił, więc to semafor faktycznie utrzymuje działanie partii.
Jak zbudować ciało JSON bez biblioteki?
Dla jednego pola wystarczy text block z escapowaną wartością. Gdy tylko ciało zawiera ciągi dostarczane przez użytkownika lub więcej niż dwa pola, użyj Jacksona albo implementacji JSON-B. Ręcznie zbudowany JSON psuje się przy pierwszym docelowym URL-u zawierającym cudzysłów, a błąd wygląda wtedy jak błąd serwera, a nie problem z formatowaniem.
Wypróbuj Elido
Wklej URL, otrzymaj krótki link
Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.
Za darmo, bez rejestracji · 2 dziennie