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