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