• 使用 Java 11 HTTP Client API 实现 HTTP/2 服务器推送


    对 HttpUrlConnection 你还有印象吗?JDK 11为 HttpUrlConnection 重新设计了 HTTP Client API。HTTP Client API 使用简单,支持 HTTP/2(默认)和 HTTP/1.1。为了向后兼容,当服务器不支持 HTTP/2时,HTTP Client API 会自动从 HTTP/2 降到 HTTP1.1。 

    此外,HTTP Client API 支持同步和异步编程模型,并依靠 stream 传输数据(reactive stream)。它还支持 WebSocket 协议,用于实时 Web 应用程序,降低客户端与服务器间通信开销。

    除了多路复用(Multiplexing),HTTP/2 另一个强大的功能是 服务器推送 。传统方法(HTTP/1.1)中,主要通过浏览器发起请求 HTML 页面,解析接收的标记(Markup)并标识引用的资源(例如JS、CSS、图像等)。 

    为了获取资源,浏览器会继续发送资源请求(每个资源一个请求)。相反,HTTP/2 会发送 HTML 页面和引用的资源,不需要浏览器主动请求。因此,浏览器请求 HTML 页面后,就能收到页面以及显示所需的所有其他信息。HTTP Client API 通过 PushPromiseHandler 接口支持 HTTP/2 功能。 

    接口实现必须作为 send() 或 sendAsync() 方法的第三个参数填入。PushPromiseHandler 依赖下面三项协同:

    • 客户端发起的 send request(initiatingRequest)

    • 合成 push request(pushPromiseRequest)

    • acceptor 函数,必须成功调用该函数才能接受 push promise(acceptor)

    调用特定 acceptor 函数接受 push promise。acceptor 函数必须传入一个 BodyHandler(不能为 null)用来处理 Promise 的 request body。acceptor 函数会返回一个 CompletableFuture 实例,完成 promise response。

    基于以上信息,看一下 PushPromiseHandler 实现:

    private 
    static final List<CompletableFuture<Void>>
      asyncPushRequests = new CopyOnWriteArrayList<>();
    ... private static HttpResponse.PushPromiseHandler<String> pushPromiseHandler() {   return (HttpRequest initiatingRequest,
         HttpRequest pushPromiseRequest,
         Function<HttpResponse.BodyHandler<String> ,
         CompletableFuture<HttpResponse<String>>> acceptor) -> {
         CompletableFuture<Void> pushcf =
            acceptor.apply(HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept((b) -> System.out.println(
            " Pushed resource body: " + b));
            asyncPushRequests.add(pushcf);
            System.out.println( " Just got promise push number: " +
              asyncPushRequests.size());
            System.out.println( " Initial push request: " +
               initiatingRequest.uri());
            System.out.println( "Initial push headers: " +
               initiatingRequest.headers());
            System.out.println( "Promise push request: " +
               pushPromiseRequest.uri());
            System.out.println( "Promise push headers: " +
               pushPromiseRequest.headers());
      };
    }

    现在,触发一个 request 把 PushPromiseHandler 传给 sendAsync():

    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create( "https://http2.golang.org/serverpush"))
      .build();
    client.sendAsync(request,
      HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
         .thenApply(HttpResponse::body)
         .thenAccept((b) -> System.out.println( " Main resource: " + b))
         .join();
    asyncPushRequests.forEach(CompletableFuture::join);
    System.out.println( " Fetched a total of " +
      asyncPushRequests.size() + " push requests");

    完整源代码可在 GitHub 上找到。

    github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPush

    如果要把所有 push promise 及 response 汇总到指定的 map 中,可以使用 PushPromiseHandler.of() 方法,如下所示:

    private
    static
     final ConcurrentMap<HttpRequest,
      CompletableFuture<HttpResponse<String>>> promisesMap
         = new ConcurrentHashMap<>(); private static final Function<HttpRequest,
      HttpResponse.BodyHandler<String>> promiseHandler
       = (HttpRequest req) -> HttpResponse.BodyHandlers.ofString(); public static void main (String[] args)
            throws IOException, InterruptedException {
      HttpClient client = HttpClient.newHttpClient();
      HttpRequest request = HttpRequest.newBuilder()
         .uri(URI.create( "https://http2.golang.org/serverpush" ))
         .build();
      client.sendAsync(request,
         HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
            .thenApply(HttpResponse::body)
            .thenAccept((b) -> System.out.println( " Main resource: " + b))
            .join(); function(){   //外汇跟单www.gendan5.com   System.out.println( " Push promises map size: " +
         promisesMap.size() + " " );
      promisesMap.entrySet().forEach((entry) -> {
         System.out.println( "Request = " + entry.getKey() +           ", Response = " + entry.getValue().join().body());
      });
    } private static HttpResponse.PushPromiseHandler<String> pushPromiseHandler() {   return HttpResponse.PushPromiseHandler.of(promiseHandler, promisesMap);
    }

    完整源代码可在 GitHub 上找到。

    github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPushToMap

    前面两个解决方案中 BodyHandler 都用到了 String 类型的 ofString()。如果服务器还需要推送二进制数据(比如图像),就不是很适用。因此,如果要处理二进制数据,则需要用 ofByteArray() 切换到byte[] 类型的 BodyHandler。也可以用 ofFile() 把 push 资源保存到磁盘,下面的解决方案是之前方案的改进版

    private 
    static final ConcurrentMap<HttpRequest,
      CompletableFuture<HttpResponse<Path>>>
         promisesMap = new ConcurrentHashMap<>(); private static final Function<HttpRequest,
      HttpResponse.BodyHandler<Path>> promiseHandler
         = (HttpRequest req) -> HttpResponse.BodyHandlers.ofFile(
           Paths.get(req.uri().getPath()).getFileName()); public static void main (String[] args)
                     throws IOException, InterruptedException {
      HttpClient client = HttpClient.newHttpClient();
      HttpRequest request = HttpRequest.newBuilder()
         .uri(URI.create( "https://http2.golang.org/serverpush"))
         .build();
      client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(
         Path.of( "index.html")), pushPromiseHandler())
            .thenApply(HttpResponse::body)
            .thenAccept((b) -> System.out.println( " Main resource: " + b))
            .join();
      System.out.println( " Push promises map size: " +
         promisesMap.size() + " ");
      promisesMap.entrySet().forEach((entry) -> {
         System.out.println( "Request = " + entry.getKey() +         ", Response = " + entry.getValue().join().body());
      });
    } private static HttpResponse.PushPromiseHandler<Path> pushPromiseHandler() {   return HttpResponse.PushPromiseHandler.of(promiseHandler, promisesMap);
    }

    上面的代码把 push 资源保存到应用程序 classpath 中, 完整源代码可在 GitHub 上找到。

    github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPushToDisk

  • 相关阅读:
    thinkphp nginx 上配置 并解决get获取到数据现象
    Linux下压缩某个文件夹(文件夹打包)
    Nginx下实现pathinfo及ThinkPHP的URL Rewrite模式支持
    SecureCRT超级终端使用说明
    redis 中文文档
    【Spring学习笔记-MVC-15】Spring MVC之异常处理
    【Spring学习笔记-MVC-14】Spring MVC对静态资源的访问
    【Spring学习笔记-MVC-13.2】Spring MVC之多文件上传
    【Spring学习笔记-MVC-13】Spring MVC之文件上传
    【Spring学习笔记-MVC-12】Spring MVC视图解析器之ResourceBundleViewResolver
  • 原文地址:https://www.cnblogs.com/gendan5/p/11770095.html
Copyright © 2020-2023  润新知