View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.ssl;
17  
18  import io.netty.channel.Channel;
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelInboundHandlerAdapter;
21  import io.netty.channel.ChannelInitializer;
22  import io.netty.channel.ChannelPipeline;
23  import io.netty.util.internal.ObjectUtil;
24  import io.netty.util.internal.logging.InternalLogger;
25  import io.netty.util.internal.logging.InternalLoggerFactory;
26  
27  /**
28   * Configures a {@link ChannelPipeline} depending on the application-level protocol negotiation result of
29   * {@link SslHandler}.  For example, you could configure your HTTP pipeline depending on the result of ALPN:
30   * <pre>
31   * public class MyInitializer extends {@link ChannelInitializer}&lt;{@link Channel}&gt; {
32   *     private final {@link SslContext} sslCtx;
33   *
34   *     public MyInitializer({@link SslContext} sslCtx) {
35   *         this.sslCtx = sslCtx;
36   *     }
37   *
38   *     protected void initChannel({@link Channel} ch) {
39   *         {@link ChannelPipeline} p = ch.pipeline();
40   *         p.addLast(sslCtx.newHandler(...)); // Adds {@link SslHandler}
41   *         p.addLast(new MyNegotiationHandler());
42   *     }
43   * }
44   *
45   * public class MyNegotiationHandler extends {@link ApplicationProtocolNegotiationHandler} {
46   *     public MyNegotiationHandler() {
47   *         super({@link ApplicationProtocolNames}.HTTP_1_1);
48   *     }
49   *
50   *     protected void configurePipeline({@link ChannelHandlerContext} ctx, String protocol) {
51   *         if ({@link ApplicationProtocolNames}.HTTP_2.equals(protocol) {
52   *             configureHttp2(ctx);
53   *         } else if ({@link ApplicationProtocolNames}.HTTP_1_1.equals(protocol)) {
54   *             configureHttp1(ctx);
55   *         } else {
56   *             throw new IllegalStateException("unknown protocol: " + protocol);
57   *         }
58   *     }
59   * }
60   * </pre>
61   */
62  public abstract class ApplicationProtocolNegotiationHandler extends ChannelInboundHandlerAdapter {
63  
64      private static final InternalLogger logger =
65              InternalLoggerFactory.getInstance(ApplicationProtocolNegotiationHandler.class);
66  
67      private final String fallbackProtocol;
68  
69      /**
70       * Creates a new instance with the specified fallback protocol name.
71       *
72       * @param fallbackProtocol the name of the protocol to use when
73       *                         ALPN/NPN negotiation fails or the client does not support ALPN/NPN
74       */
75      protected ApplicationProtocolNegotiationHandler(String fallbackProtocol) {
76          this.fallbackProtocol = ObjectUtil.checkNotNull(fallbackProtocol, "fallbackProtocol");
77      }
78  
79      @Override
80      public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
81          if (evt instanceof SslHandshakeCompletionEvent) {
82              ctx.pipeline().remove(this);
83  
84              SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
85              if (handshakeEvent.isSuccess()) {
86                  SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
87                  if (sslHandler == null) {
88                      throw new IllegalStateException("cannot find a SslHandler in the pipeline (required for " +
89                                                      "application-level protocol negotiation)");
90                  }
91                  String protocol = sslHandler.applicationProtocol();
92                  configurePipeline(ctx, protocol != null? protocol : fallbackProtocol);
93              } else {
94                  handshakeFailure(ctx, handshakeEvent.cause());
95              }
96          }
97  
98          ctx.fireUserEventTriggered(evt);
99      }
100 
101     /**
102      * Invoked on successful initial SSL/TLS handshake. Implement this method to configure your pipeline
103      * for the negotiated application-level protocol.
104      *
105      * @param protocol the name of the negotiated application-level protocol, or
106      *                 the fallback protocol name specified in the constructor call if negotiation failed or the client
107      *                 isn't aware of ALPN/NPN extension
108      */
109     protected abstract void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception;
110 
111     /**
112      * Invoked on failed initial SSL/TLS handshake.
113      */
114     protected void handshakeFailure(ChannelHandlerContext ctx, Throwable cause) throws Exception {
115         logger.warn("{} TLS handshake failed:", ctx.channel(), cause);
116         ctx.close();
117     }
118 
119     @Override
120     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
121         logger.warn("{} Failed to select the application-level protocol:", ctx.channel(), cause);
122         ctx.close();
123     }
124 }