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