View Javadoc
1   /*
2    * Copyright 2012 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.netty5.bootstrap;
17  
18  import io.netty5.channel.Channel;
19  import io.netty5.channel.ChannelHandler;
20  import io.netty5.channel.ChannelHandlerContext;
21  import io.netty5.channel.ChannelInitializer;
22  import io.netty5.channel.ChannelOption;
23  import io.netty5.channel.ChannelPipeline;
24  import io.netty5.channel.EventLoop;
25  import io.netty5.channel.EventLoopGroup;
26  import io.netty5.channel.ReflectiveServerChannelFactory;
27  import io.netty5.channel.ServerChannel;
28  import io.netty5.channel.ServerChannelFactory;
29  import io.netty5.util.AttributeKey;
30  import io.netty5.util.concurrent.Future;
31  import io.netty5.util.concurrent.Promise;
32  import io.netty5.util.internal.logging.InternalLogger;
33  import io.netty5.util.internal.logging.InternalLoggerFactory;
34  
35  import java.util.LinkedHashMap;
36  import java.util.Map;
37  import java.util.Map.Entry;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.concurrent.TimeUnit;
40  
41  import static java.util.Objects.requireNonNull;
42  
43  /**
44   * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
45   *
46   */
47  public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel,
48          ServerChannelFactory<? extends ServerChannel>> {
49  
50      private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
51  
52      // The order in which child ChannelOptions are applied is important they may depend on each other for validation
53      // purposes.
54      private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<>();
55      private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<>();
56      private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
57      private volatile EventLoopGroup childGroup;
58      private volatile ChannelHandler childHandler;
59      volatile ServerChannelFactory<? extends ServerChannel> channelFactory;
60  
61      public ServerBootstrap() { }
62  
63      private ServerBootstrap(ServerBootstrap bootstrap) {
64          super(bootstrap);
65          childGroup = bootstrap.childGroup;
66          childHandler = bootstrap.childHandler;
67          channelFactory = bootstrap.channelFactory;
68          synchronized (bootstrap.childOptions) {
69              childOptions.putAll(bootstrap.childOptions);
70          }
71          childAttrs.putAll(bootstrap.childAttrs);
72      }
73  
74      /**
75       * Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client).
76       */
77      @Override
78      public ServerBootstrap group(EventLoopGroup group) {
79          return group(group, group);
80      }
81  
82      /**
83       * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
84       * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
85       * {@link Channel}'s.
86       */
87      public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
88          super.group(parentGroup);
89          requireNonNull(childGroup, "childGroup");
90          if (this.childGroup != null) {
91              throw new IllegalStateException("childGroup set already");
92          }
93          this.childGroup = childGroup;
94          return this;
95      }
96  
97      /**
98       * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created
99       * (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set
100      * {@link ChannelOption}.
101      */
102     public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
103         requireNonNull(childOption, "childOption");
104         synchronized (childOptions) {
105             if (value == null) {
106                 childOptions.remove(childOption);
107             } else {
108                 childOptions.put(childOption, value);
109             }
110         }
111         return this;
112     }
113 
114     /**
115      * Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
116      * {@code null} the {@link AttributeKey} is removed
117      */
118     public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
119         requireNonNull(childKey, "childKey");
120         if (value == null) {
121             childAttrs.remove(childKey);
122         } else {
123             childAttrs.put(childKey, value);
124         }
125         return this;
126     }
127 
128     /**
129      * Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s.
130      */
131     public ServerBootstrap childHandler(ChannelHandler childHandler) {
132         requireNonNull(childHandler, "childHandler");
133         this.childHandler = childHandler;
134         return this;
135     }
136 
137     /**
138      * The {@link Class} which is used to create {@link ServerChannel} instances from.
139      * You either use this or {@link #channelFactory(ServerChannelFactory)} if your
140      * {@link Channel} implementation has no no-args constructor.
141      */
142     public ServerBootstrap channel(Class<? extends ServerChannel> channelClass) {
143         requireNonNull(channelClass, "channelClass");
144         return channelFactory(new ReflectiveServerChannelFactory<ServerChannel>(channelClass));
145     }
146 
147     /**
148      * {@link ServerChannelFactory} which is used to create {@link Channel} instances from
149      * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
150      * is not working for you because of some more complex needs. If your {@link Channel} implementation
151      * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} to
152      * simplify your code.
153      */
154     public ServerBootstrap channelFactory(ServerChannelFactory<? extends ServerChannel> channelFactory) {
155         requireNonNull(channelFactory, "channelFactory");
156         if (this.channelFactory != null) {
157             throw new IllegalStateException("channelFactory set already");
158         }
159 
160         this.channelFactory = channelFactory;
161         return this;
162     }
163 
164     @Override
165     Future<Channel> init(Channel channel) {
166         Promise<Channel> promise = channel.executor().newPromise();
167         setChannelOptions(channel, newOptionsArray(), logger);
168         setAttributes(channel, newAttributesArray());
169 
170         ChannelPipeline p = channel.pipeline();
171 
172         final ChannelHandler currentChildHandler = childHandler;
173         final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
174         final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
175 
176         p.addLast(new ChannelInitializer<>() {
177             @Override
178             public void initChannel(final Channel ch) {
179                 final ChannelPipeline pipeline = ch.pipeline();
180                 ChannelHandler handler = config.handler();
181                 if (handler != null) {
182                     pipeline.addLast(handler);
183                 }
184 
185                 ch.executor().execute(() -> {
186                     pipeline.addLast(new ServerBootstrapAcceptor(
187                             ch, currentChildHandler, currentChildOptions, currentChildAttrs));
188                     promise.setSuccess(ch);
189                 });
190             }
191         });
192         return promise.asFuture();
193     }
194 
195     @Override
196     ServerChannel newChannel(EventLoop eventLoop) throws Exception {
197         return channelFactory.newChannel(eventLoop, childGroup);
198     }
199 
200     @Override
201     public ServerBootstrap validate() {
202         super.validate();
203         if (childHandler == null) {
204             throw new IllegalStateException("childHandler not set");
205         }
206         if (channelFactory == null) {
207             throw new IllegalStateException("channelFactory not set");
208         }
209         if (childGroup == null) {
210             logger.warn("childGroup is not set. Using parentGroup instead.");
211             childGroup = config.group();
212         }
213         return this;
214     }
215 
216     private static class ServerBootstrapAcceptor implements ChannelHandler {
217 
218         private final ChannelHandler childHandler;
219         private final Entry<ChannelOption<?>, Object>[] childOptions;
220         private final Entry<AttributeKey<?>, Object>[] childAttrs;
221         private final Runnable enableAutoReadTask;
222 
223         ServerBootstrapAcceptor(
224                 final Channel channel, ChannelHandler childHandler,
225                 Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
226             this.childHandler = childHandler;
227             this.childOptions = childOptions;
228             this.childAttrs = childAttrs;
229 
230             // Task which is scheduled to re-enable auto-read.
231             // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
232             // not be able to load the class because of the file limit it already reached.
233             //
234             // See https://github.com/netty/netty/issues/1328
235             enableAutoReadTask = () -> channel.setOption(ChannelOption.AUTO_READ, true);
236         }
237 
238         @Override
239         public void channelRead(ChannelHandlerContext ctx, Object msg) {
240             final Channel child = (Channel) msg;
241 
242             EventLoop childEventLoop = child.executor();
243             // Ensure we always execute on the child EventLoop.
244             if (childEventLoop.inEventLoop()) {
245                 initChild(child);
246             } else {
247                 try {
248                     childEventLoop.execute(() -> initChild(child));
249                 } catch (Throwable cause) {
250                     forceClose(child, cause);
251                 }
252             }
253         }
254 
255         private void initChild(final Channel child) {
256             assert child.executor().inEventLoop();
257             try {
258                 setChannelOptions(child, childOptions, logger);
259                 setAttributes(child, childAttrs);
260 
261                 child.pipeline().addLast(childHandler);
262 
263                 child.register().addListener(future -> {
264                     if (future.isFailed()) {
265                         forceClose(child, future.cause());
266                     }
267                 });
268             } catch (Throwable t) {
269                 forceClose(child, t);
270             }
271         }
272 
273         private static void forceClose(Channel child, Throwable t) {
274             child.close();
275             logger.warn("Failed to register an accepted channel: {}", child, t);
276         }
277 
278         @Override
279         public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
280             if (ctx.channel().getOption(ChannelOption.AUTO_READ)) {
281                 // stop accept new connections for 1 second to allow the channel to recover
282                 // See https://github.com/netty/netty/issues/1328
283                 ctx.channel().setOption(ChannelOption.AUTO_READ, false);
284                 ctx.channel().executor().schedule(enableAutoReadTask, 1, TimeUnit.SECONDS);
285             }
286             // still let the exceptionCaught event flow through the pipeline to give the user
287             // a chance to do something with it
288             ctx.fireChannelExceptionCaught(cause);
289         }
290     }
291 
292     @Override
293     @SuppressWarnings("CloneDoesntCallSuperClone")
294     public ServerBootstrap clone() {
295         return new ServerBootstrap(this);
296     }
297 
298     /**
299      * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
300      * if non is configured yet.
301      *
302      * @deprecated Use {@link #config()} instead.
303      */
304     @Deprecated
305     public EventLoopGroup childGroup() {
306         return childGroup;
307     }
308 
309     final ChannelHandler childHandler() {
310         return childHandler;
311     }
312 
313     final Map<ChannelOption<?>, Object> childOptions() {
314         synchronized (childOptions) {
315             return copiedMap(childOptions);
316         }
317     }
318 
319     final Map<AttributeKey<?>, Object> childAttrs() {
320         return copiedMap(childAttrs);
321     }
322 
323     @Override
324     public final ServerBootstrapConfig config() {
325         return config;
326     }
327 }