последовательность
В этой статье в основном рассматриваются основы использования HttpClient в Java 11.
Разнообразие
- Перейдите с модуля jdk.incubator.httpclient java9 на модуль java.net.http, имя пакета изменится с jdk.incubator.http на java.net.http.
- Исходные методы, такие как HttpResponse.BodyHandler.asString(), были изменены на HttpResponse.BodyHandlers.ofString(), первым изменением было BodyHandler на BodyHandlers, вторым изменением было asXXX(), а другие методы были изменены на ofXXX(), от as к из
пример
установить тайм-аут
@Test
public void testTimeout() throws IOException, InterruptedException {
//1.set connect timeout
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
//2.set read timeout
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMillis(5009))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
- Экземпляр HttpConnectTimeoutException
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1248)
at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:877)
Caused by: java.net.ConnectException: HTTP connect timed out
at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
... 2 more
- Экземпляр HttpTimeoutException
java.net.http.HttpTimeoutException: request timed out
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:559)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
at com.example.HttpClientTest.testTimeout(HttpClientTest.java:40)
установить аутентификатор
@Test
public void testBasicAuth() throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("admin","password".toCharArray());
}
})
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/json/info"))
.timeout(Duration.ofMillis(5009))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
- аутентификатор может использоваться для установки HTTP-аутентификации, например обычной аутентификации.
- Хотя Базовая аутентификация тоже может задавать сам заголовок, но через аутентификатор можно избавить себя от построения заголовка
установить заголовок
@Test
public void testCookies() throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/json/cookie"))
.header("Cookie","JSESSIONID=4f994730-32d7-4e22-a18b-25667ddeb636; userId=java11")
.timeout(Duration.ofMillis(5009))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
- Вы можете установить заголовок самостоятельно через запрос
GET
- Синхронизировать
@Test
public void testSyncGet() throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com"))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
- асинхронный
@Test
public void testAsyncGet() throws ExecutionException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com"))
.build();
CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
System.out.println(result.get());
}
ПОЧТОВАЯ форма
@Test
public void testPostForm() throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://www.w3school.com.cn/demo/demo_form.asp"))
.header("Content-Type","application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString("name1=value1&name2=value2"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
}
- Заголовок указывает, что содержимое является типом формы, а затем передает данные формы через BodyPublishers.ofString, вам необходимо самостоятельно построить параметры формы
POST JSON
@Test
public void testPostJsonGetJson() throws ExecutionException, InterruptedException, JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
StockDto dto = new StockDto();
dto.setName("hj");
dto.setSymbol("hj");
dto.setType(StockDto.StockType.SH);
String requestBody = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(dto);
HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/json/demo"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
CompletableFuture<StockDto> result = HttpClient.newHttpClient()
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(body -> {
try {
return objectMapper.readValue(body,StockDto.class);
} catch (IOException e) {
return new StockDto();
}
});
System.out.println(result.get());
}
- В случае поста json само тело json конвертируется в строку, а затем заголовок указывается в формате json
Файл загружен
@Test
public void testUploadFile() throws IOException, InterruptedException, URISyntaxException {
HttpClient client = HttpClient.newHttpClient();
Path path = Path.of(getClass().getClassLoader().getResource("body.txt").toURI());
File file = path.toFile();
String multipartFormDataBoundary = "Java11HttpClientFormBoundary";
org.apache.http.HttpEntity multipartEntity = MultipartEntityBuilder.create()
.addPart("file", new FileBody(file, ContentType.DEFAULT_BINARY))
.setBoundary(multipartFormDataBoundary) //要设置,否则阻塞
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/file/upload"))
.header("Content-Type", "multipart/form-data; boundary=" + multipartFormDataBoundary)
.POST(HttpRequest.BodyPublishers.ofInputStream(() -> {
try {
return multipartEntity.getContent();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
- Официальный HttpClient не предоставляет готовый метод BodyInserters.fromMultipartData наподобие WebClient, поэтому вам нужно конвертировать его самостоятельно
- Здесь мы используем org.apache.httpcomponents(
httpclient及httpmime
) из MultipartEntityBuilder для создания multipartEntity и, наконец, передачи содержимого через HttpRequest.BodyPublishers.ofInputStream. - Заголовок здесь должен указывать значение Content-Type как multipart/form-data и border, иначе сервер не сможет его проанализировать.
Загрузка файла
@Test
public void testAsyncDownload() throws ExecutionException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/file/download"))
.build();
CompletableFuture<Path> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(Paths.get("/tmp/body.txt")))
.thenApply(HttpResponse::body);
System.out.println(result.get());
}
- Используйте HttpResponse.BodyHandlers.ofFile для получения файлов
одновременные запросы
@Test
public void testConcurrentRequests(){
HttpClient client = HttpClient.newHttpClient();
List<String> urls = List.of("http://www.baidu.com","http://www.alibaba.com/","http://www.tencent.com");
List<HttpRequest> requests = urls.stream()
.map(url -> HttpRequest.newBuilder(URI.create(url)))
.map(reqBuilder -> reqBuilder.build())
.collect(Collectors.toList());
List<CompletableFuture<HttpResponse<String>>> futures = requests.stream()
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
.collect(Collectors.toList());
futures.stream()
.forEach(e -> e.whenComplete((resp,err) -> {
if(err != null){
err.printStackTrace();
}else{
System.out.println(resp.body());
System.out.println(resp.statusCode());
}
}));
CompletableFuture.allOf(futures
.toArray(CompletableFuture<?>[]::new))
.join();
}
- Метод sendAsync возвращает CompletableFuture, который можно легко преобразовать, объединить и т. д.
- Здесь используйте CompletableFuture.allOf для объединения и, наконец, вызовите join, чтобы дождаться завершения всех фьючерсов.
обработка ошибок
@Test
public void testHandleException() throws ExecutionException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://twitter.com"))
.build();
CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
// .whenComplete((resp,err) -> {
// if(err != null){
// err.printStackTrace();
// }else{
// System.out.println(resp.body());
// System.out.println(resp.statusCode());
// }
// })
.thenApply(HttpResponse::body)
.exceptionally(err -> {
err.printStackTrace();
return "fallback";
});
System.out.println(result.get());
}
- Асинхронный запрос HttpClient возвращает CompletableFuture
, который имеет собственный исключительный метод, который можно использовать для резервной обработки. - Также стоит отметить, что HttpClient не похож на WebClient, он не генерирует исключения для кодов состояния 4xx или 5xx, вам нужно обрабатывать его в зависимости от ситуации, вручную определять код состояния, чтобы генерировать исключения или возвращать другой контент.
HTTP2
@Test
public void testHttp2() throws URISyntaxException {
HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.version(HttpClient.Version.HTTP_2)
.build()
.sendAsync(HttpRequest.newBuilder()
.uri(new URI("https://http2.akamai.com/demo"))
.GET()
.build(),
HttpResponse.BodyHandlers.ofString())
.whenComplete((resp,t) -> {
if(t != null){
t.printStackTrace();
}else{
System.out.println(resp.version());
System.out.println(resp.statusCode());
}
}).join();
}
- После выполнения вы можете увидеть, что версия возвращаемого ответа — HTTP_2.
WebSocket
@Test
public void testWebSocket() throws InterruptedException {
HttpClient client = HttpClient.newHttpClient();
WebSocket webSocket = client.newWebSocketBuilder()
.buildAsync(URI.create("ws://localhost:8080/echo"), new WebSocket.Listener() {
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
// request one more
webSocket.request(1);
// Print the message when it's available
return CompletableFuture.completedFuture(data)
.thenAccept(System.out::println);
}
}).join();
webSocket.sendText("hello ", false);
webSocket.sendText("world ",true);
TimeUnit.SECONDS.sleep(10);
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join();
}
- HttpClient поддерживает HTTP2, а также включает WebSocket и создает WebSocket с помощью newWebSocketBuilder.
- Передайте прослушиватель, чтобы получить сообщение.Если вы хотите отправить сообщение, используйте WebSocket для его отправки и используйте метод sendClose, чтобы закрыть его.
reactive streams
Сам HttpClient является реактивным и поддерживает реактивные потоки Вот исходный код ResponseSubscribers.ByteArraySubscriber: java.net.http/jdk/internal/net/http/ResponseSubscribers.java
public static class ByteArraySubscriber<T> implements BodySubscriber<T> {
private final Function<byte[], T> finisher;
private final CompletableFuture<T> result = new MinimalFuture<>();
private final List<ByteBuffer> received = new ArrayList<>();
private volatile Flow.Subscription subscription;
public ByteArraySubscriber(Function<byte[],T> finisher) {
this.finisher = finisher;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
if (this.subscription != null) {
subscription.cancel();
return;
}
this.subscription = subscription;
// We can handle whatever you've got
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(List<ByteBuffer> items) {
// incoming buffers are allocated by http client internally,
// and won't be used anywhere except this place.
// So it's free simply to store them for further processing.
assert Utils.hasRemaining(items);
received.addAll(items);
}
@Override
public void onError(Throwable throwable) {
received.clear();
result.completeExceptionally(throwable);
}
static private byte[] join(List<ByteBuffer> bytes) {
int size = Utils.remaining(bytes, Integer.MAX_VALUE);
byte[] res = new byte[size];
int from = 0;
for (ByteBuffer b : bytes) {
int l = b.remaining();
b.get(res, from, l);
from += l;
}
return res;
}
@Override
public void onComplete() {
try {
result.complete(finisher.apply(join(received)));
received.clear();
} catch (IllegalArgumentException e) {
result.completeExceptionally(e);
}
}
@Override
public CompletionStage<T> getBody() {
return result;
}
}
- Интерфейс BodySubscriber наследует интерфейс Flow.Subscriber
- .
- Подписка здесь исходит из класса Flow, который был представлен java9 и содержит реализацию, поддерживающую Reactive Streams.
резюме
HttpClient изменился с инкубатора на официальную версию в Java 11. По сравнению с традиционным HttpUrlConnection его улучшение не на шутку, он не только поддерживает асинхронность, но и поддерживает реактивные потоки, а также поддерживает HTTP2 и WebSocket, что очень стоит использовать.
doc
- java.net.http javadoc
- Examples and Recipes
- Java 11: Standardized HTTP Client API
- Exploring the New HTTP Client in Java 9
- Introduction to the New HTTP Client in Java 9
- Getting Started With Java 9's New HTTP Client
- java9 series (шесть) Клиент HTTP/2 (инкубатор)
- Java 9 HttpClient send a multipart/form-data request
- Java 9: High level HTTP and WebSocket API
- WebSocket Client API in Java 9 with Example