ALPN介绍
ALPN(Application Layer Protocol Negotiation)是TLS的扩展协议,用于应用层协议的协商。
chrome浏览器和curl是支持ALPN的,在发起HTTPS请求时通过ALPN发起请求的协议为h2, http/1.1。为了支持HTTP2的升级和兼容ALPN的才诞生的。
ALPN环境支持
JDK9才原生在API和库上支持ALPN,JDK9以下可以通过其他途径支持,比如使用netty的库和jetty。jetty实现ALPN参见:http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn
通过ALPN协商使用MQTT或者HTTP1.1
下面方案只是低于JDK9实现ALPN的一种方式。通过jetty实现ALPN的方案参见
http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn,通过netty也能支持ALPN,在此不再描述。
- 使低于JDK9的JVM支持ALPN
CATALINA_OPTS="${CATALINA_OPTS} -Xbootclasspath/p:/opt/java/lib/alpn-boot-8.1.11.v20170118.jar"
jar根据JVM版本的不同而不同 - 添加依赖
org.eclipse.jetty.alpn
alpn-api
1.0.0
provided
org.eclipse.jetty
jetty-alpn-openjdk8-server
9.4.8.v20171121
MySslHandler.java
package alpn.server;
import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine;
/**
* 一种方法是通过Netty的ApplicationProtocolNegotiationHandler获得ALPN协商之后的数据,但是使用jetty-alpn,并不能获得协商之后的协议(sslHandler.applicationProtocol返回的为空)
* 所以使用该方式实现。
*/
public class MySslHandler extends SslHandler {
/**
* 选择的协议
*/
private volatile String selectedProtocol;
public MySslHandler(SSLEngine sslEngine){
super(sslEngine);
}
public String getSelectedProtocol() {
return selectedProtocol;
}
public void setSelectedProtocol(String selectedProtocol) {
this.selectedProtocol = selectedProtocol;
}
}
ServerPipelineInitializer.java
package alpn.server;
import io.netty.channel.*;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.mqtt.MqttDecoder;
import io.netty.handler.codec.mqtt.MqttEncoder;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.timeout.IdleStateHandler;
import org.eclipse.jetty.alpn.ALPN;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.util.List;
/**
*/
public class ServerPipelineInitializer extends ChannelInitializer {
private static final String HTTP_1_1="http/1.1";
private static final String HTTP_2="h2";
@Override
protected void initChannel(Channel ch) throws Exception {
SSLContext sslContext=null;//先设置sslContext
ChannelPipeline pipeline=ch.pipeline();
pipeline.addLast("sslHandler",getMySSlHandler(sslContext));
//ALPN
pipeline.addLast("alpnHandler" ,new ChannelDuplexHandler() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslHandshakeCompletionEvent) {
ctx.pipeline().remove(this);
//sslHandler握手结束会触发SslHandshakeCompletionEvent事件
SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
if (handshakeEvent.isSuccess()) {
MySslHandler mySslHandler = (MySslHandler)ctx.pipeline().get("sslHandler");
String protocol = mySslHandler.getSelectedProtocol();
//HTTP1.1
if (HTTP_1_1.equals(protocol)) {
pipeline.addLast(new HttpResponseEncoder());
// 客户端接收到的是httpResponse响应,所以要使用HttpResponseDecoder进行解码
pipeline.addLast(new HttpRequestDecoder());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new IdleStateHandler(5000, 5000, 0));
//业务处理器
pipeline.addLast(new HttpServiceHandler());
} else {
//MQTT的编码和解码器
pipeline.addLast(MqttEncoder.INSTANCE);
pipeline.addLast(new MqttDecoder());
//业务处理器
}
}
}else{
ctx.channel().close();
}
}
});
}
public MySslHandler getMySSlHandler(SSLContext sslContext){
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(false);
final MySslHandler mySslHandler = new MySslHandler(sslEngine);
//JDK9才原生支持ALPN,JDK7和JDK8通过依赖其他库实现。这里使用jetty实现
ALPN.put(sslEngine, new ALPN.ServerProvider() {
@Override
public void unsupported() {
ALPN.remove(sslEngine);
}
@Override
public String select(List protocols) {
ALPN.remove(sslEngine);
if(protocols.size()==2&&protocols.contains(HTTP_1_1)&&protocols.contains(HTTP_2)){
mySslHandler.setSelectedProtocol(HTTP_1_1);
return HTTP_1_1;
}else if(protocols.size()==1&&protocols.contains(HTTP_1_1)){
mySslHandler.setSelectedProtocol(HTTP_1_1);
return HTTP_1_1;
}else{
throw new RuntimeException("alpn error");
}
}
});
return mySslHandler;
}
}
HttpServiceHandler.java
package alpn.server;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*/
public class HttpServiceHandler extends ChannelDuplexHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpRequest request = (FullHttpRequest) msg;
String uri = request.getUri();
if (!HttpMethod.POST.equals(request.getMethod())) {
send(null, ctx.channel(), HttpResponseStatus.METHOD_NOT_ALLOWED);
return;
}
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request);
Map params = new HashMap<>();
List parmList = decoder.getBodyHttpDatas();
for (InterfaceHttpData parm : parmList) {
if (parm.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
Attribute attribute = (Attribute) parm;
params.put(attribute.getName(), attribute.getValue());
}
}
//设备初始化通道服务
if ("/service/init.do".equals(uri)) {
send("hello world", ctx.channel(), HttpResponseStatus.OK);
} else {
//404
send(null, ctx.channel(), HttpResponseStatus.NOT_FOUND);
}
//释放buffer
ctx.fireChannelRead(msg);
}
private void send(String content, Channel channel, HttpResponseStatus status) throws Exception {
FullHttpResponse fullHttpResponse = null;
if (content==null) {
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
} else {
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(content.getBytes("utf-8")));
}
fullHttpResponse.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json");
fullHttpResponse.headers().set(HttpHeaders.Names.CONTENT_LENGTH, fullHttpResponse.content().readableBytes());
fullHttpResponse.headers().set(HttpHeaders.Names.CONNECTION, false);
channel.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}