ALPN

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,在此不再描述。

  1. 使低于JDK9的JVM支持ALPN
    CATALINA_OPTS="${CATALINA_OPTS} -Xbootclasspath/p:/opt/java/lib/alpn-boot-8.1.11.v20170118.jar"
    jar根据JVM版本的不同而不同
  2. 添加依赖
                    
                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);
    }
}

你可能感兴趣的:(ALPN)