简介

我们知道虽然HTTP2协议并不强制使用HTTPS,但是对大多数浏览器来说,如果要使用HTTP2的话,则必须使用HTTPS,所以我们需要了解如何在netty的TLS中支持http2。

TLS的扩展协议NPN和ALPN

HTTP2协议是从spdy协议发展而来的,无论是spdy还是http2都为了能在HTTPS的环境下工作,发展出来了TLS协议的扩展。

他们分别叫做NPN(Next Protocol Negotiation) 和 ALPN (Application Layer Protocol Negotiation) 。

他们规定了在TLS协议握手之后,客户端和服务器端进行应用数据通信的协议。其中ALPN可以在客户端首次和服务器端进行握手的时候,就列出客户端支持的应用层数据协议,服务器端直接选择即可,因此可以比NPN少一个交互流程,更加优秀。

那么spdy和http2分别支持的协议都有哪些呢?

netty提供了一个ApplicationProtocolNames类,在其中定义了各自对应的协议,其中ALPN对应了http2和http1.1,而sydy对应了spdy/1,spdy/2,spdy/3:


/**
* HTTP version 2
*/
public static final String HTTP_2 = "h2"; /**
* {@code "http/1.1"}: HTTP version 1.1
*/
public static final String HTTP_1_1 = "http/1.1"; /**
* {@code "spdy/3.1"}: SPDY version 3.1
*/
public static final String SPDY_3_1 = "spdy/3.1"; /**
* {@code "spdy/3"}: SPDY version 3
*/
public static final String SPDY_3 = "spdy/3"; /**
* {@code "spdy/2"}: SPDY version 2
*/
public static final String SPDY_2 = "spdy/2"; /**
* {@code "spdy/1"}: SPDY version 1
*/
public static final String SPDY_1 = "spdy/1";

SslProvider

目前来说,netty中有两种SSL的实现方式,一种是JDK,一种是OPENSSL,不同的实现方式对TLS协议扩展的支持也不一样。它提供了一个isAlpnSupported方法,根据传入provider的不同来判断,是否支持ALPN。

    public static boolean isAlpnSupported(final SslProvider provider) {
switch (provider) {
case JDK:
return JdkAlpnApplicationProtocolNegotiator.isAlpnSupported();
case OPENSSL:
case OPENSSL_REFCNT:
return OpenSsl.isAlpnSupported();
default:
throw new Error("Unknown SslProvider: " + provider);
}
}

如果你使用的是JDK8,那么运行之后,可能会得到下面的错误提示:

ALPN is only supported in Java9 or if you use conscrypt as your provider or have the jetty alpn stuff on the class path.

也就是说如果是用JDK作为默认的SSL provider的话,它是不支持ALPN的。必须升级到java9.

根据提示如果添加conscrypt到classpath中:

        <dependency>
<groupId>org.conscrypt</groupId>
<artifactId>conscrypt-openjdk-uber</artifactId>
<version>2.5.2</version>
</dependency>

运行之后会得到下面的错误:

Unable to wrap SSLEngine of type 'sun.security.ssl.SSLEngineImpl'

怎么办呢?答案就是使用Open SSL,还需要添加:

        <dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.40.Final</version>
</dependency>

经过测试,完美执行。

ApplicationProtocolConfig

ApplicationProtocolConfig是netty提供了传递给SSLEngine的协议配置类,它主要有四个属性:

    private final List<String> supportedProtocols;
private final Protocol protocol;
private final SelectorFailureBehavior selectorBehavior;
private final SelectedListenerFailureBehavior selectedBehavior;

supportedProtocols是支持的数据传输协议,像上面的HTTP2,HTTP1.1或者spdy/1,spdy/2,spdy/3等。

protocol是TLS的扩展协议,像ALPN或者NPN等。

selectorBehavior是在选择协议的时候的表现方式,有3种方式:

FATAL_ALERT: 如果选择应用程序协议的节点没有找到匹配项,那么握手将会失败。

NO_ADVERTISE: 如果选择应用程序协议的节点没有找到匹配项,它将通过在握手中假装不支持 TLS 扩展。

CHOOSE_MY_LAST_PROTOCOL: 如果选择应用程序协议的节点没有找到匹配项,将会使用上一次建议使用的协议。

selectedBehavior是通知被选择的协议之后的表现方式,也有3种方式:

ACCEPT: 如果节点不支持对方节点选择的应用程序协议,则该节点默认不支持该TLS扩展,然后继续握手。

FATAL_ALERT: 如果节点不支持对方节点选择的应用程序协议,则握手失败。

CHOOSE_MY_LAST_PROTOCOL: 如果节点不支持对方节点选择的应用程序协议,将会使用上一次建议使用的协议。

构建SslContext

有了provider,ApplicationProtocolConfig 之后,就可以构建SslContext了。首先创建SSL provider:

 SslProvider provider =  SslProvider.isAlpnSupported(SslProvider.OPENSSL)  ? SslProvider.OPENSSL : SslProvider.JDK;

默认情况下使用JDK作为ssl provider,如果你使用的是OpenSSL的话,就使用OpenSSL。

我们使用SslContextBuilder.forServer来创建SslContext,这个方法需要传入certificate和privateKey,为了简单起见,我们使用自签名的SelfSignedCertificate:

 SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();

还可以为其设置sslProvider,ciphers和applicationProtocolConfig等信息:

sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
.sslProvider(provider)
//支持的cipher
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.applicationProtocolConfig(new ApplicationProtocolConfig(
Protocol.ALPN,
// 目前 OpenSsl 和 JDK providers只支持NO_ADVERTISE
SelectorFailureBehavior.NO_ADVERTISE,
// 目前 OpenSsl 和 JDK providers只支持ACCEPT
SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1))
.build();

ProtocolNegotiationHandler

最后,我们需要根据协商使用的不同协议,进行不同的处理。netty提供了一个ApplicationProtocolNegotiationHandler,自定义的话,只需要继承该类即可,比如,我们根据protocol的名称不同,来分别处理HTTP1和HTTP2请求:

   public class MyNegotiationHandler extends ApplicationProtocolNegotiationHandler {
public MyNegotiationHandler() {
super(ApplicationProtocolNames.HTTP_1_1);
} protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
if (ApplicationProtocolNames.HTTP_2.equals(protocol) {
configureHttp2(ctx);
} else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
configureHttp1(ctx);
} else {
throw new IllegalStateException("unknown protocol: " + protocol);
}
}
}

然后将其加入到ChannelPipeline中即可:

   public class MyInitializer extends ChannelInitializer<Channel> {
private final SslContext sslCtx; public MyInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
} protected void initChannel(Channel ch) {
ChannelPipeline p = ch.pipeline();
p.addLast(sslCtx.newHandler(...)); // Adds SslHandler
p.addLast(new MyNegotiationHandler());
}
}

总结

以上就是在netty中配置TLS支持HTTP2的完整流程了。

本文的例子可以参考:learn-netty4

本文已收录于 http://www.flydean.com/26-netty-secure-http2/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

netty系列之:让TLS支持http2的更多相关文章

  1. netty系列之:使用netty实现支持http2的服务器

    目录 简介 基本流程 CleartextHttp2ServerUpgradeHandler Http2ConnectionHandler 总结 简介 上一篇文章中,我们提到了如何在netty中配置TL ...

  2. netty系列之:手持framecodec神器,创建多路复用http2客户端

    目录 简介 配置SslContext 客户端的handler 使用Http2FrameCodec Http2MultiplexHandler和Http2MultiplexCodec 使用子channe ...

  3. netty系列之:性能为王!创建多路复用http2服务器

    目录 简介 多路复用的基础 多路复用在server端的使用 配置TLS处理器 配置clear text upgrade 总结 简介 在之前的文章中,我们提到了在netty的客户端通过使用Http2Fr ...

  4. netty系列之:搭建客户端使用http1.1的方式连接http2服务器

    目录 简介 使用http1.1的方式处理http2 处理TLS连接 处理h2c消息 发送消息 总结 简介 对于http2协议来说,它的底层跟http1.1是完全不同的,但是为了兼容http1.1协议, ...

  5. Netty 系列九(支持UDP协议).

    一.基础知识 UDP 协议相较于 TCP 协议的特点: 1.无连接协议,没有持久化连接:2.每个 UDP 数据报都是一个单独的传输单元:3.一定的数据报丢失:4.没有重传机制,也不管数据报是否可达:5 ...

  6. netty系列之:netty架构概述

    目录 简介 netty架构图 丰富的Buffer数据机构 零拷贝 统一的API 事件驱动 其他优秀的特性 总结 简介 Netty为什么这么优秀,它在JDK本身的NIO基础上又做了什么改进呢?它的架构和 ...

  7. 从零开始搭建支持http2的web服务

    前段时间开始,公司各项业务开始陆续接入http2,关于http2的优点与所适用的场景网上有很多的文档可以查阅,这里我主要是总结分享一下如何从0到1搭建http2服务. 这里先说明一下,要完成http2 ...

  8. Netty 系列七(那些开箱即用的 ChannelHandler).

    一.前言 Netty 为许多通用协议提供了编解码器和处理器,几乎可以开箱即用, 这减少了你在那些相当繁琐的事务上本来会花费的时间与精力.另外,这篇文章中,就不涉及 Netty 对 WebSocket协 ...

  9. Netty 系列目录

    Netty 系列目录 二 Netty 源码分析(4.1.20) 1.1 Netty 源码(一)Netty 组件简介 2.1 Netty 源码(一)服务端启动 2.2 Netty 源码(二)客户端启动 ...

随机推荐

  1. 洛谷P1083 借教室 题解

    题目 [NOIP2012 提高组] 借教室 题解 这道题是几周之前做到的一道题,本来不想讲的,因为这道题也是用到了二分答案的方法,这类题目之前已经发布过两篇题解了.但这道题还运用了差分数组这个思想,所 ...

  2. css文本溢出省略号大总结,如你所愿

    一行: white-space: nowrap; text-overflow: ellipsis; overflow: hidden; word-break: break-all; 两行: width ...

  3. noip模拟35

    A. 玩游戏 考场做法用双指针向两侧更新,当左段点左移一位时,如果右端点不满足条件,则跳回肯定满足的位置.复杂度玄学 题解做法是类似最长子段和,如果有一个区间和为负,则维护的指针跳过去即可 B. 排列 ...

  4. L2TP协议简介

    传送门:L2TP代码实现 1. L2TP 概述 L2TP(Layer 2 Tunneling Protocol,二层隧道协议)是 VPDN(Virtual Private Dial-up Networ ...

  5. RabbitMQ核心知识总结!

    本文已经收录到github仓库,此仓库用于分享Java相关知识总结,包括Java基础.MySQL.Spring Boot.MyBatis.Redis.RabbitMQ.计算机网络.数据结构与算法等等, ...

  6. kubectl apply部署时可以用 --record 方便记录版本 和回退

    1.部署时正常时下面的 kubectl apply -f http.yaml 2.如果修改文件文件重新部署或者之前有上一个版本的  想回退上一个的 可以无感知的回退回去 不影响业务 其中http-de ...

  7. Marvell 88SE9215 AHCI驱动笔记

    禁止转载!禁止转载!禁止转载! 一.Marvell 88SE9215.AHCI与SATA简介 1.Marvell 88SE9215 1)概述 88SE9215是一个四端口,兼容3 Gbps和6 Gbp ...

  8. 自己实现一个Controller——精简型

    写在最前 controller-manager作为K8S master的其中一个组件,负责众多controller的启动和终止,这些controller负责监控着k8s中各种资源,执行调谐,使他们的实 ...

  9. 【redis前传】集思广益之quicklist,取其精华去其糟粕

    前言 在之前我们已经学习了redis五大数据结构中的list结构.其内部是linkedList和zipList两种结构.这是我们已经学习的内容.之前我没有结合操作具体查看.事实上在两者中还存在一种结合 ...

  10. PHP中类的自动加载

    在之前,我们已经学习过Composer自动加载的原理,其实就是利用了PHP中的类自动加载的特性.在文末有该系列文章的链接. PHP中类的自动加载主要依靠的是__autoload()和spl_autol ...