sequence

This article focuses on the basic use of Java11 HttpClient.

change

  • Migrate from the JDk.incubator. Httpclient module of Java9 to the java.net.http module with the package name changed from jdk.incubator
  • The original such as HttpResponse. BodyHandler. AsString () method is changed to HttpResponse. BodyHandlers. OfString (), change one for BodyHandler BodyHandlers instead, The second change is that methods like asXXX() are changed to ofXXX(), and as is changed to of

The instance

Setting timeout

    @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());

    }
Copy the code
  • HttpConnectTimeoutException instance
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
Copy the code
  • HttpTimeoutException instance
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)
Copy the code

Set the authenticator

    @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());
    }
Copy the code
  • Authenticator can be used to set HTTP authentication, such as Basic Authentication
  • Although Basic Authentication can also set its own headers, using authenticator saves you from having to construct the headers yourself

Set the header

    @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());
    }
Copy the code
  • Request allows you to set your own headers

GET

  • synchronous
    @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());
    }
Copy the code
  • asynchronous
    @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());
    }
Copy the code

POST form

    @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());
    }
Copy the code
  • The header specifies that the content is a form type, and then passes the form data through BodyPublishers. OfString, requiring you to build the form parameters yourself

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) {
                        returnnew StockDto(); }}); System.out.println(result.get()); }Copy the code
  • If you post JSON, the body itself is jsonized as a string, and then the header specifies that it’s in JSON format

File upload

    @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) / / to set, or blocked. The 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());
    }
Copy the code
  • The official HttpClient does not provide a similar WebClient the ready-made BodyInserters. FromMultipartData method, thus their conversion is needed here
  • Here use org. Apache httpcomponents (Httpclient and httpmime) MultipartEntityBuilder build multipartEntity, at last, an HttpRequest BodyPublishers. OfInputStream to deliver content
  • Header must specify the content-type value as multipart/form-data and the value of boundary, otherwise the server may not be able to parse

File download

    @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());
    }
Copy the code
  • Use of HttpResponse. BodyHandlers. OfFile to receive the file

Concurrent requests

    @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();
    }
Copy the code
  • The sendAsync method returns the CompletableFuture, making it easy to transform, compose, and so on
  • This is done using completableFuture. allOf, and then calls Join to wait for all futures to complete

Error handling

    @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());
    }
Copy the code
  • The HttpClient asynchronous request returns CompletableFuture

    , and the exceptionally method is used for fallback processing
  • It is also worth noting that HttpClient, unlike WebClient, does not throw an exception to the 4XX or 5XX status code

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();
    }
Copy the code
  • After execution, you can see that the version of response returned is 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(); }Copy the code
  • HttpClient supports HTTP2 and also includes WebSocket, which is constructed by newWebSocketBuilder
  • The listener is passed in to receive a message. To send a message, use WebSocket and disable sendClose

reactive streams

HttpClient itself is reactive, support reactive streams, ResponseSubscribers cited here. ByteArraySubscriber source to see: 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
      
        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
      
        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
       
         getBody() { return result; }}
       
      Copy the code
  • The BodySubscriber interface inherits the flow. Subscriber interface
  • The Subscription here comes from the Flow class, which was introduced in Java9 and contains implementations that support Reactive Streams

summary

HttpClient was upgraded from The traditional HttpUrlConnection to the Incubator in Java11, supporting asynchronous and Reactive Streams as well as HTTP2 and WebSocket. It’s worth using.

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 (6)HTTP/2 Client (Incubator)
  • 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