关注网络安全
和行业未来

Java 9 HTTP/2的支持功能前瞻

Java 9预期将于本月27日发布,本文由László Csontos发布在Springuni,讨论了Java 9与HTTP/2有关的特性。

在HTTP/1.1发布后16年,国际互联网工程任务组(IETF)的流媒体工作组在2015年批准了HTTP/2协议。HTTP/2承诺降低网络延迟,并弃用很多在HTTP/1.1中所必需的工作流程,以满足当今的响应时间要求。本文将简要介绍HTTP/2,并谈谈它是怎样使基于文本的HTTP/1.1变得焕然一新的,以及即将来到的Java 9中对HTTP/2的支持功能。

HTTP/1.1早已变得老旧不堪,而1999年以来的互联网已经发生了很多变化。

在互联网上,人们正变得越来越没有耐心,但他们不会注意到,如果响应时间低于100毫秒,那么他们在网络上表现出的行为并不直接由他们自己实施。

当响应时间达到1秒时,就会引起人们的注意了,而当一个网站的响应时间超过10秒时,那么它就会被认为发生了故障。根据一些调查的结果,人们注意力的平均持续时间已经下降到7-8秒,所以即使是1秒的延迟也会造成高达7%的收入损失。

针对HTTP/1.1的网络延迟优化技术

对于HTTP/1.1来说,需要做一些(有时候是很繁重的)工作来满足当今的要求。

  • 由于每个HTTP连接每次只能下载一个资源,所以浏览器为了能够更快地显示页面,就要同时取回这些资源。但是,每个域的并发连接的数量是有限的,而域分片技术(domain sharding)的存在就是用来实现这一目的。
  • 一项类似的优化技术是将多个资源(CSS、JavaScript)捆绑在一起,这样就能够通过一次单独的请求获取到这些资源。这样做好坏参半,在节省了一次网络往返过程的同时,可能会使捆绑资源中的一部分无法使用。在某些情况下,复杂的服务器端逻辑负责选择适当的静态资源,并将它们合并以响应特殊的页面请求。
  • Image sprites图像精灵是一项与CSS和JavaScript文件捆绑类似的技术,可以减少请求的数量。
  • 还有另一项技术被用于将静态资源内嵌到HTML中。

HTTP/2概述

HTTP/2意在减轻为维护HTTP/1.1复杂的底层架构而带来的痛苦,以提高HTTP/1.1的性能。尽管HTTP/2仍然对HTTP/1.1向下兼容,但它已不再是一个基于文本的协议。当客户端通过HTTP/1.1请求建立一个连接时,所有请求将会被升级。从这一点上看,HTTP/2是用“二进制数据帧”来说话的。

HTTP/2多路复用

HTTP/2多路复用使得一个单独连接可以处理多个双向流,因而客户端可以通过一个单独连接同时下载多个不同的资源。

HTTP/2头部压缩

HTTP 1.x协议族都是基于文本的,因此它们都相当冗长。有时候同一个HTTP头的集合被一遍又一遍地进行交换。HTTP/2在整个请求过程中保持HTTP头 表不变,因而大大降低了所需的带宽。重要的是,这只是在去耦合,而不是经典意义上的压缩。

HTTP/2推送

你可能会认为,HTTP/2推送是某种WebSocket的延续或升级,但实际上并不是这样。WebSocket是客户端和服务器之间全双工通信的一种方法,一旦TCP连接被建立起来,服务器就可以向客户端发送数据,而HTTP/2则解决的是与此不同的问题。

HTTP/2推送一种主动向客户端发送资源的技术,不必由客户端发出请求。这实际上意味着,服务器端知道,一个网站需要一些图片,服务器会在客户端发出请求前的很长时间内,就一次性将这些图片发送到客户端。

Java HTTP客户端支持HTTP/2

根据维基百科关于HTTP/2的一个页面的说法,在编写的时候,以下Java客户端库已能够建立HTTP/2连接。

  • Jetty
  • Netty
  • OkHttp
  • Vert.x
  • Firefly

但在这篇文章中,我们关注的是Java 9提供的HTTP/2支持。JEP 110 指定了具体要求,同时声明该项目仍处于孵化状态,这实际上意味着,它将不会取代Java 9中现有的UrlConnection API。 只有在Java 10发布后,标准Java HTTP/2客户端才会被移动到java.net包之下。但同时,它会处于jdk.incubtor命名空间下。

JEP 110为新的、内置的HTTP/2客户端提出了具体要求,因此它提供了一个高级别的、简便易用的API和与现有选项相似(或更高)的性能。

第一步是导入模块jdk.incubator.httpclient

1

2

3

module com.springui.echo.client {

requires jdk.incubator.httpclient;

}

 

对于这个例子来说,我们要使用Undertow做为兼容HTTP/2的web服务器。它用来回应客户端发送的消息。

public class EchoServer {

 

private static final Logger LOGGER = Logger.getLogger(EchoServer.class.getSimpleName());

 

private static final int PORT = 8888;

private static final String HOST = "localhost";

 

public static void main(final String[] args) {

Undertow server = Undertow.builder()

.setServerOption(UndertowOptions.ENABLE_HTTP2, true)

.addHttpListener(PORT, HOST)

.setHandler(exchange -> {

LOGGER.info("Client address is: " + exchange.getConnection().getPeerAddress().toString());

exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");

exchange.getRequestReceiver().receiveFullString((e, m) -> e.getResponseSender().send(m));

}).build();

 

server.start();

}

 

}

新的API处处遵循生成器模式,而作为初始化HTTP请求入口的HttpClient也不例外。

HttpClient client = HttpClient

.newBuilder()

.version(Version.HTTP_2)

.build();

以阻塞模式发送请求

一旦我们有了一个HttpClient实例,就可以通过一个生成器建立更多的HttpClient实例。

HttpResponse<String> response = client.send(

HttpRequest

.newBuilder(TEST_URI)

.POST(BodyProcessor.fromString("Hello world"))

.build(),

BodyHandler.asString()

);

请求被处理多久,send方法就会阻塞多久,但还是有一种方法来异步交换HTTP消息:以非阻塞模式发送请求。

以非阻塞模式发送请求

在下面的例子中,10个随机整数被异步发送到我们的HTTP回显服务器,而当所有请求被初始化后,主线程等待它们完成。

List<CompletableFuture<String>> responseFutures = new Random()

.ints(10)

.mapToObj(String::valueOf)

.map(message -> client

.sendAsync(

HttpRequest.newBuilder(TEST_URI)

.POST(BodyProcessor.fromString(message))

.build(),

BodyHandler.asString()

)

.thenApply(r -> r.body())

)

.collect(Collectors.toList());

 

CompletableFuture.allOf(responseFutures.toArray(new CompletableFuture<?>[0])).join();

 

responseFutures.stream().forEach(future -> {

LOGGER.info("Async response: " + future.getNow(null));

});

处理push-promise架构

以上全部例子都可以是过时的HTTP/1.1请求。除了创建HttpClient以外,没有看到任何HTTP/2所特有的东西。

这个客户端API中与HTTP/2最有关联的功能很可能是当HTTP/2推送被使用时它处理多个响应的方式。

Map<HttpRequest, CompletableFuture<HttpResponse<String>>> responses =

client.sendAsync(

HttpRequest.newBuilder(TEST_URI)

.POST(BodyProcessor.fromString(TEST_MESSAGE))

.build(),

MultiProcessor.asMap(request -> Optional.of(BodyHandler.asString()))

).join();

 

responses.forEach((request, responseFuture) -> {

LOGGER.info("Async response: " + responseFuture.getNow(null));

});

结论

HTTP/2进行了一些必要改进,使旧的基于文本的协议变得焕然一新,并抛弃了令人讨厌的HTTP/1.1中的很多工作流程,但是它并未解决所有已知的问题。

从Java 9方面来看,新的HTTP/2客户端貌似不错,但它的下一个版本才会是合格的产品。同时,如果需要HTTP/2支持的话,上面的库都可以使用。

稿源:Springuni.com

评论 1

  1. #1

    有改进已经很好了

    匿名2周前 (07-06)回复