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