Set up HTTP/2 server with Spring Boot 【基于Spring boot搭建http2.0服务器】

时间:2022-05-19 04:07:49

1. Server side

With spring boot, we can set up a http server easily. Restcontroller make it easier to provide REST apis.

今天博主决定体验下HTTP/2。用spring boot搭建http服务相对容易,于是开始动手编写。

Entrance (入口)

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}

Configuration (配置)

Marked sentence enables http/2

标记部分可以为undertow容器开启H2的支持

@Configuration
public class BaseConfig {
    @Bean
    public EmbeddedServletContainerFactory servletContainer() {
        UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
        factory.addBuilderCustomizers(builder -> builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
        return factory;
    }
}

Controller

@RestController
public class SimpleController {

    @RequestMapping("/greeting")
    public String greeting(@RequestParam(value="name", defaultValue="World") String name) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'Hello World'.concat('~')");
        String message = exp.getValue(String.class);

        return message;
    }

    @RequestMapping("/hello")
    public ResponseEntity hello(@RequestParam(value="name", defaultValue = "SYSTEM") String name) {
        return ResponseEntity.ok().body("Hello from " + name);
    }
}

Let's try it! Start server, use okhttp client to visit one link localhost:8080/greeting, and then wait for the response. Surprisingly, the http protocol is still http/1.1. What happened? Why there is no magic after enabling http/2 support? Well, JDK 8 still lacks good support for http/2.

编写完成,启动服务器,用okhttp客户端访问其中一个url。虽然有所期待,然而并无惊喜,从响应来看协议还是1.1。经过一番调查发现,即便是最新的JDK8,对H2的支持也不好。(听说JDK9会支持)

The good news is, we can start server with alpn jar to make up for this limitation. Let's config the following argument in jvm. Remember to choose the proper jar version that matches your jre version. https://www.eclipse.org/jetty/documentation/9.4.x/alpn-chapter.html#alpn-starting

好在Jetty alpn为JDK 8提供了alpn支持,我们在jvm的启动参数里加上下面这句

-Xbootclasspath/p:{maven dir}/repository/org/mortbay/jetty/alpn/alpn-boot/8.1.3.v20150130/alpn-boot-8.1.3.v20150130.jar

Again, the response shows the protocol is http/1.1. Thanks to the blog https://daniel.haxx.se/blog/2015/03/06/tls-in-http2/, it reminds us most existing servers only speak http/2 via TLS.

OK, let's make our Spring boot support https as well. First generate a SSL cert, and then configure the keystore infos in spring properties.

可是重启服务器之后,协议还是1.1。这次又是为什么呢?原来,虽说h2的规范中并没有强行要求一定要在传输层加密,然而现有实现基本都建立在安全传输层协议上的。 所以要搭建H2服务器,我们还差最后一步 ------ 支持安全传输层协议。

将http改为https很容易,创建证书,给spring加上相关配置,重启服务便完成了。

server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=123456
server.ssl.keyStoreType=PKCS12

Finally, an HTTP/2 is running successfully.

一波三折,H2服务器终于搞定。

2. Client side

Next, let's use okhttp client to experience http/2.

Using default okhttp client will encounter this error.

接着,用okhttp客户端来感受下新协议。出师不利,一开始访问就遇到下面的错误。

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Luckily, we can customize unsafe http client to trust self signed cert.

刚才给服务器配的证书是自签名的,客户端不让轻易连接。暂时不想动服务器的证书,那么就降低客户端安全性吧,按照下面的代码配置一个安全性较低的客户端。

private static OkHttpClient getUnsafeOkHttpClient() {
        try {
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[]{};
                }
            };
            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[] {trustManager};

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
            builder.sslSocketFactory(sslSocketFactory, trustManager);
            builder.hostnameVerifier((hostname, session) -> true);
return builder.build(); } catch (Exception e) { throw new RuntimeException(e); } }

Then, the client prompts below info. So we add the same argument in client side jvm as what has been done in server side.

再次访问服务器,出现了新的警告。提示很明显,所以我们参照之前的方式给客户端jvm也加上启动参数。

Apr 27, 2017 5:19:43 PM okhttp3.internal.platform.Platform log
INFO: ALPN callback dropped: HTTP/2 is disabled. Is alpn-boot on the boot class path?
Apr 27, 2017 5:19:43 PM okhttp3.internal.platform.Platform log
INFO: ALPN callback dropped: HTTP/2 is disabled. Is alpn-boot on the boot class path?

After so many fails, the expected result finally appears in the console. Okhttp client tells the current prototol becomes h2 (http/2.0).

最后,终端上显示出了期待已久的H2

h2
Time consumed in ts 2159
h2
Time consumed in ts 2160

  

3. Some benefits after using h2 client

https://http2.github.io/faq/#why-revise-http this passage already explains a lot.

Why just one TCP connection?

With HTTP/1, browsers open between four and eight connections per origin. Since many sites use multiple origins, this could mean that a single page load opens more than thirty connections.

One application opening so many connections simultaneously breaks a lot of the assumptions that TCP was built upon; since each connection will start a flood of data in the response, there’s a real risk that buffers in the intervening network will overflow, causing a congestion event and retransmits.

Additionally, using so many connections unfairly monopolizes network resources, “stealing” them from other, better-behaved applications (e.g., VoIP).

While only one communication is built per origin, it consumes less resource in both client and server.

Here we conduct a simple check of the network communications.

H2的优点很多,这里主要验证下连接开销这块。

With http 1.1

用1.1客户端给服务器发送几十个请求,可以看到许多tcp建立了起来,这同时增加了客户端和服务器的开销。

C:\Users\??>netstat | find "8080"
  TCP    127.0.0.1:8080         ??:10951            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10952            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10953            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10954            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10955            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10956            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10957            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10958            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10959            ESTABLISHED
  TCP    127.0.0.1:8080         ??:10960            ESTABLISHED
  TCP    127.0.0.1:10951        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10952        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10953        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10954        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10955        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10956        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10957        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10958        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10959        ??:8080             ESTABLISHED
  TCP    127.0.0.1:10960        ??:8080             ESTABLISHED

With http 2.0

使用2.0客户端,果然只有一个连接了。

C:\Users\??>netstat | find "8080"
  TCP    127.0.0.1:8080         ??:11571            ESTABLISHED
  TCP    127.0.0.1:11571        ??:8080             ESTABLISHED

The connection count reduced obviously after changing the protocol.

Apart from that, slow start, one congestion controll stratege, happens only one during the lifecycle. It's good the client performance and network.

由于同一个连接复用,慢启动只发生一次,这对客户端和网络也有一定好处。