View Javadoc
1   /*
2    * Copyright 2017 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    *   https://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.codec.http2;
17  
18  import io.netty.channel.Channel;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelHandler;
22  import io.netty.channel.ChannelHandlerContext;
23  import io.netty.channel.ChannelOption;
24  import io.netty.channel.ChannelPipeline;
25  import io.netty.util.AttributeKey;
26  import io.netty.util.concurrent.EventExecutor;
27  import io.netty.util.concurrent.Future;
28  import io.netty.util.concurrent.Promise;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.StringUtil;
31  import io.netty.util.internal.logging.InternalLogger;
32  import io.netty.util.internal.logging.InternalLoggerFactory;
33  
34  import java.nio.channels.ClosedChannelException;
35  import java.util.LinkedHashMap;
36  import java.util.Map;
37  import java.util.concurrent.ConcurrentHashMap;
38  
39  public final class Http2StreamChannelBootstrap {
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2StreamChannelBootstrap.class);
41      @SuppressWarnings("unchecked")
42      private static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
43      @SuppressWarnings("unchecked")
44      private static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];
45  
46      // The order in which ChannelOptions are applied is important they may depend on each other for validation
47      // purposes.
48      private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
49      private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
50      private final Channel channel;
51      private volatile ChannelHandler handler;
52  
53      // Cache the ChannelHandlerContext to speed up open(...) operations.
54      private volatile ChannelHandlerContext multiplexCtx;
55  
56      public Http2StreamChannelBootstrap(Channel channel) {
57          this.channel = ObjectUtil.checkNotNull(channel, "channel");
58      }
59  
60      /**
61       * Allow to specify a {@link ChannelOption} which is used for the {@link Http2StreamChannel} instances once they got
62       * created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
63       */
64      public <T> Http2StreamChannelBootstrap option(ChannelOption<T> option, T value) {
65          ObjectUtil.checkNotNull(option, "option");
66  
67          synchronized (options) {
68              if (value == null) {
69                  options.remove(option);
70              } else {
71                  options.put(option, value);
72              }
73          }
74          return this;
75      }
76  
77      /**
78       * Allow to specify an initial attribute of the newly created {@link Http2StreamChannel}.  If the {@code value} is
79       * {@code null}, the attribute of the specified {@code key} is removed.
80       */
81      public <T> Http2StreamChannelBootstrap attr(AttributeKey<T> key, T value) {
82          ObjectUtil.checkNotNull(key, "key");
83          if (value == null) {
84              attrs.remove(key);
85          } else {
86              attrs.put(key, value);
87          }
88          return this;
89      }
90  
91      /**
92       * the {@link ChannelHandler} to use for serving the requests.
93       */
94      public Http2StreamChannelBootstrap handler(ChannelHandler handler) {
95          this.handler = ObjectUtil.checkNotNull(handler, "handler");
96          return this;
97      }
98  
99      /**
100      * Open a new {@link Http2StreamChannel} to use.
101      * @return the {@link Future} that will be notified once the channel was opened successfully or it failed.
102      */
103     public Future<Http2StreamChannel> open() {
104         return open(channel.eventLoop().<Http2StreamChannel>newPromise());
105     }
106 
107     /**
108      * Open a new {@link Http2StreamChannel} to use and notifies the given {@link Promise}.
109      * @return the {@link Future} that will be notified once the channel was opened successfully or it failed.
110      */
111     public Future<Http2StreamChannel> open(final Promise<Http2StreamChannel> promise) {
112         try {
113             ChannelHandlerContext ctx = findCtx();
114             EventExecutor executor = ctx.executor();
115             if (executor.inEventLoop()) {
116                 open0(ctx, promise);
117             } else {
118                 final ChannelHandlerContext finalCtx = ctx;
119                 executor.execute(new Runnable() {
120                     @Override
121                     public void run() {
122                         if (channel.isActive()) {
123                             open0(finalCtx, promise);
124                         } else {
125                             promise.setFailure(new ClosedChannelException());
126                         }
127                     }
128                 });
129             }
130         } catch (Throwable cause) {
131             promise.setFailure(cause);
132         }
133         return promise;
134     }
135 
136     @SuppressWarnings("deprecation")
137     private ChannelHandlerContext findCtx() throws ClosedChannelException {
138         // First try to use cached context and if this not work lets try to lookup the context.
139         ChannelHandlerContext ctx = multiplexCtx;
140         if (ctx != null && !ctx.isRemoved()) {
141             return ctx;
142         }
143         ChannelPipeline pipeline = channel.pipeline();
144         ctx = pipeline.context(Http2MultiplexCodec.class);
145         if (ctx == null) {
146             ctx = pipeline.context(Http2MultiplexHandler.class);
147         }
148         if (ctx == null) {
149             if (channel.isActive()) {
150                 throw new IllegalStateException(StringUtil.simpleClassName(Http2MultiplexCodec.class) + " or "
151                         + StringUtil.simpleClassName(Http2MultiplexHandler.class)
152                         + " must be in the ChannelPipeline of Channel " + channel);
153             } else {
154                 throw new ClosedChannelException();
155             }
156         }
157         multiplexCtx = ctx;
158         return ctx;
159     }
160 
161     /**
162      * @deprecated should not be used directly. Use {@link #open()} or {@link #open(Promise)}
163      */
164     @Deprecated
165     public void open0(ChannelHandlerContext ctx, final Promise<Http2StreamChannel> promise) {
166         assert ctx.executor().inEventLoop();
167         if (!promise.setUncancellable()) {
168             return;
169         }
170         final Http2StreamChannel streamChannel;
171         try {
172             if (ctx.handler() instanceof Http2MultiplexCodec) {
173                 streamChannel = ((Http2MultiplexCodec) ctx.handler()).newOutboundStream();
174             } else {
175                 streamChannel = ((Http2MultiplexHandler) ctx.handler()).newOutboundStream();
176             }
177         } catch (Exception e) {
178             promise.setFailure(e);
179             return;
180         }
181         try {
182             init(streamChannel);
183         } catch (Exception e) {
184             streamChannel.unsafe().closeForcibly();
185             promise.setFailure(e);
186             return;
187         }
188 
189         ChannelFuture future = ctx.channel().eventLoop().register(streamChannel);
190         future.addListener(new ChannelFutureListener() {
191             @Override
192             public void operationComplete(ChannelFuture future) {
193                 if (future.isSuccess()) {
194                     promise.setSuccess(streamChannel);
195                 } else if (future.isCancelled()) {
196                     promise.cancel(false);
197                 } else {
198                     if (streamChannel.isRegistered()) {
199                         streamChannel.close();
200                     } else {
201                         streamChannel.unsafe().closeForcibly();
202                     }
203 
204                     promise.setFailure(future.cause());
205                 }
206             }
207         });
208     }
209 
210     private void init(Channel channel) {
211         ChannelPipeline p = channel.pipeline();
212         ChannelHandler handler = this.handler;
213         if (handler != null) {
214             p.addLast(handler);
215         }
216         final Map.Entry<ChannelOption<?>, Object> [] optionArray;
217         synchronized (options) {
218             optionArray = options.entrySet().toArray(EMPTY_OPTION_ARRAY);
219         }
220 
221         setChannelOptions(channel, optionArray);
222         setAttributes(channel, attrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
223     }
224 
225     private static void setChannelOptions(
226             Channel channel, Map.Entry<ChannelOption<?>, Object>[] options) {
227         for (Map.Entry<ChannelOption<?>, Object> e: options) {
228             setChannelOption(channel, e.getKey(), e.getValue());
229         }
230     }
231 
232     private static void setChannelOption(
233             Channel channel, ChannelOption<?> option, Object value) {
234         try {
235             @SuppressWarnings("unchecked")
236             ChannelOption<Object> opt = (ChannelOption<Object>) option;
237             if (!channel.config().setOption(opt, value)) {
238                 logger.warn("Unknown channel option '{}' for channel '{}'", option, channel);
239             }
240         } catch (Throwable t) {
241             logger.warn(
242                     "Failed to set channel option '{}' with value '{}' for channel '{}'", option, value, channel, t);
243         }
244     }
245 
246     private static void setAttributes(
247             Channel channel, Map.Entry<AttributeKey<?>, Object>[] options) {
248         for (Map.Entry<AttributeKey<?>, Object> e: options) {
249             @SuppressWarnings("unchecked")
250             AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
251             channel.attr(key).set(e.getValue());
252         }
253     }
254 }