Java11新特性之HttpClient小试牛刀

本文主要研究一下Java11的HttpClient的基本使用。

变化

  1. 从java9的jdk.incubator.httpclient模块迁移到java.net.http模块,包名由jdk.incubator.http改为java.net.http
  2. 原来的诸如HttpResponse.BodyHandler.asString()方法变更为HttpResponse.BodyHandlers.ofString(),变化一为BodyHandler改为BodyHandlers,变化二为asXXX()之类的方法改为ofXXX(),由as改为of

实例

设置超时时间

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

设置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());
 }
  1. authenticator可以用来设置HTTP authentication,比如Basic authentication
  2. 虽然Basic authentication也可以自己设置header,不过通过authenticator省得自己去构造header

设置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());
 }

通过request可以自己设置header

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

POST表单

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

header指定内容是表单类型,然后通过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());
 }

post json的话,body自己json化为string,然后header指定是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());
 }
  1. 官方的HttpClient并没有提供类似WebClient那种现成的BodyInserters.fromMultipartData方法,因此这里需要自己转换
  2. 这里使用org.apache.httpcomponents(httpclient及httpmime)的MultipartEntityBuilder构建multipartEntity,最后通过HttpRequest.BodyPublishers.ofInputStream来传递内容
  3. 这里header要指定Content-Type值为multipart/form-data以及boundary的值,否则服务端可能无法解析

文件下载

 @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等待所有future完成

错误处理

 @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<HttpResponse<T>>,其自带exceptionally方法可以用来做fallback处理
  • 另外值得注意的是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();
 }

执行之后可以看到返回的response的version为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,通过newWebSocketBuilder去构造WebSocket
  • 传入listener进行接收消息,要发消息的话,使用WebSocket来发送,关闭使用sendClose方法

reactive streams

HttpClient本身就是reactive的,支持reactive streams,这里举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;
  }
 }
  1. BodySubscriber接口继承了Flow.Subscriber<List<ByteBuffer>>接口
  2. 这里的Subscription来自Flow类,该类是java9引入的,里头包含了支持Reactive Streams的实现

小结

HttpClient在Java11从incubator变为正式版,相对于传统的HttpUrlConnection其提升可不是一点半点,不仅支持异步,也支持reactive streams,同时也支持了HTTP2以及WebSocket,非常值得大家使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java 10的10个新特性总结

    Java 9才发布几个月,很多玩意都没整明白,现在Java 10又要来了. 这时候我真想说:线上用的JDK 7,甚至JDK 6,而JDK 8 还没用熟,JDK 9 才发布不久不知道啥玩意,JDK 10-- 刚学Java的同学是不是感觉一脸蒙逼? 就连我这个老司机也同样感觉如此! Java 更新越来越快,我们做技术的也要跟上步伐,不然总会慢别人一拍,这新东西从国外到国内应用一般要好几年的时间,如果我们提前了解并应用这些新技术对自己不是坏事. Java 10的新特性 说了这么多,看Java 10都会

  • Java8新特性Lambda表达式的一些复杂用法总结

    简介 lambda表达式是JAVA8中提供的一种新的特性,它支持Java也能进行简单的"函数式编程". 它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数. 本文将介绍关于Java8 Lambda表达式的一些复杂用法,分享出来供大家参考学习,下面来一起看看详细的介绍: 复杂用法实例 传入数组ids,在list<Obj>上操作,找出Obj中id想匹配的,并且按

  • Java8深入学习系列(三)你可能忽略了的新特性

    前言 我们之前已经介绍了关于java8中lambda和函数式编程的相关内容,虽然我们开始了Java8的旅程,但是很多人直接从java6上手了java8, 也许有一些JDK7的特性你还不知道,在本章节中带你回顾一下我们忘记了的那些特性. 尽管我们不能讲所有特性都讲一遍,挑出常用的核心特性拎出来一起学习. 异常改进 try-with-resources 这个特性是在JDK7种出现的,我们在之前操作一个流对象的时候大概是这样的: try { // 使用流对象 stream.read(); stream

  • Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码

    美国时间 09 月 25 日,Oralce 正式发布了 Java 11,这是据 Java 8 以后支持的首个长期版本.非常值得大家的关注,可以通过下面的地址进行下载: https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html 为什么说是长期版本,看下面的官方发布的支持路线图表. 可以看出 Java 8 扩展支持到 2025 年,而 Java 11 扩展支持到 2026 年. 现在大部

  • Java8中接口的新特性测试

    在Java SE 8之前,接口中是不能提供方法实现的,但是JDK8提供了接口的默认方法和静态方法的支持. 默认方法 方法前加default关键字就可以提供默认实现,类实现接口时,可以继承接口的默认方法,也可以覆盖默认方法. interface People { default void eat(String name) { System.out.println(name + " is eating."); } } 抽象类也可以提供方法的默认实现,一个类可以同时继承一个抽象类和多个接口,

  • Java11 发布前抓紧掌握这些新特性

    快速回顾 1.Lambda表达式: (参数) -> {主体} Lambda表达式打开了函数式编程爱好者继续使用Java的大门.Lambda表达式需要零个或多个参数,这些参数可以在表达式主体中访问,并随计算结果一起返回. Comparator<Integer> comparator = (a, b) -> a-b; System.out.println(comparator.compare(3, 4)); // -1 2. 函数接口:只有一种方法的接口 lambda表达式本身被视为可

  • 详解Java8 新特性之日期API

    Java 8 在包java.time下包含了一组全新的时间日期API.下面的例子展示了这组新API里最重要的一些部分: 1.Clock 时钟 Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数.某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象. Clock clock = Clock.systemDefaultZone();

  • Java8中新特性Optional、接口中默认方法和静态方法详解

    前言 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性. Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习.下面本文就给大家详细介绍了Java8中新特性Optional.接口中默认方法和静态方法的相关内容,话不多说了,来一起看看详细的介绍吧. Optional Optional 类(java.util.Optional) 是一个

  • Java8新特性Stream流实例详解

    什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

  • 详解Java8新特性之interface中的static方法和default方法

    为什么要单独写个Java8新特性,一个原因是我目前所在的公司用的是jdk8,并且框架中用了大量的Java8的新特性,如上篇文章写到的stream方法进行过滤map集合.stream方法就是接口Collection中的default方法.所以准备专门写写关于java8新特性的文章,虽然现在10已经发布了.但还是要认真的去了解下新版本的变化. static方法 java8中为接口新增了一项功能:定义一个或者更多个静态方法.用法和普通的static方法一样. 代码示例 public interface

随机推荐