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}<{@link Channel}> {
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 }