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.StringUtil;
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.TimeUnit;
39  
40  /**
41   * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
42   *
43   */
44  public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
45  
46      private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
47  
48      private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
49      private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
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     /**
141      * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
142      * if non is configured yet.
143      */
144     public EventLoopGroup childGroup() {
145         return childGroup;
146     }
147 
148     @Override
149     void init(Channel channel) throws Exception {
150         final Map<ChannelOption<?>, Object> options = options();
151         synchronized (options) {
152             setChannelOptions(channel, options, logger);
153         }
154 
155         final Map<AttributeKey<?>, Object> attrs = attrs();
156         synchronized (attrs) {
157             for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
158                 @SuppressWarnings("unchecked")
159                 AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
160                 channel.attr(key).set(e.getValue());
161             }
162         }
163 
164         ChannelPipeline p = channel.pipeline();
165 
166         final EventLoopGroup currentChildGroup = childGroup;
167         final ChannelHandler currentChildHandler = childHandler;
168         final Entry<ChannelOption<?>, Object>[] currentChildOptions;
169         final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
170         synchronized (childOptions) {
171             currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
172         }
173         synchronized (childAttrs) {
174             currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
175         }
176 
177         p.addLast(new ChannelInitializer<Channel>() {
178             @Override
179             public void initChannel(final Channel ch) throws Exception {
180                 final ChannelPipeline pipeline = ch.pipeline();
181                 ChannelHandler handler = handler();
182                 if (handler != null) {
183                     pipeline.addLast(handler);
184                 }
185 
186                 ch.eventLoop().execute(new Runnable() {
187                     @Override
188                     public void run() {
189                         pipeline.addLast(new ServerBootstrapAcceptor(
190                                 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
191                     }
192                 });
193             }
194         });
195     }
196 
197     @Override
198     public ServerBootstrap validate() {
199         super.validate();
200         if (childHandler == null) {
201             throw new IllegalStateException("childHandler not set");
202         }
203         if (childGroup == null) {
204             logger.warn("childGroup is not set. Using parentGroup instead.");
205             childGroup = group();
206         }
207         return this;
208     }
209 
210     @SuppressWarnings("unchecked")
211     private static Entry<AttributeKey<?>, Object>[] newAttrArray(int size) {
212         return new Entry[size];
213     }
214 
215     @SuppressWarnings("unchecked")
216     private static Map.Entry<ChannelOption<?>, Object>[] newOptionArray(int size) {
217         return new Map.Entry[size];
218     }
219 
220     private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
221 
222         private final EventLoopGroup childGroup;
223         private final ChannelHandler childHandler;
224         private final Entry<ChannelOption<?>, Object>[] childOptions;
225         private final Entry<AttributeKey<?>, Object>[] childAttrs;
226         private final Runnable enableAutoReadTask;
227 
228         ServerBootstrapAcceptor(
229                 final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
230                 Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
231             this.childGroup = childGroup;
232             this.childHandler = childHandler;
233             this.childOptions = childOptions;
234             this.childAttrs = childAttrs;
235 
236             // Task which is scheduled to re-enable auto-read.
237             // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
238             // not be able to load the class because of the file limit it already reached.
239             //
240             // See https://github.com/netty/netty/issues/1328
241             enableAutoReadTask = new Runnable() {
242                 @Override
243                 public void run() {
244                     channel.config().setAutoRead(true);
245                 }
246             };
247         }
248 
249         @Override
250         @SuppressWarnings("unchecked")
251         public void channelRead(ChannelHandlerContext ctx, Object msg) {
252             final Channel child = (Channel) msg;
253 
254             child.pipeline().addLast(childHandler);
255 
256             setChannelOptions(child, childOptions, logger);
257 
258             for (Entry<AttributeKey<?>, Object> e: childAttrs) {
259                 child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
260             }
261 
262             try {
263                 childGroup.register(child).addListener(new ChannelFutureListener() {
264                     @Override
265                     public void operationComplete(ChannelFuture future) throws Exception {
266                         if (!future.isSuccess()) {
267                             forceClose(child, future.cause());
268                         }
269                     }
270                 });
271             } catch (Throwable t) {
272                 forceClose(child, t);
273             }
274         }
275 
276         private static void forceClose(Channel child, Throwable t) {
277             child.unsafe().closeForcibly();
278             logger.warn("Failed to register an accepted channel: " + child, t);
279         }
280 
281         @Override
282         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
283             final ChannelConfig config = ctx.channel().config();
284             if (config.isAutoRead()) {
285                 // stop accept new connections for 1 second to allow the channel to recover
286                 // See https://github.com/netty/netty/issues/1328
287                 config.setAutoRead(false);
288                 ctx.channel().eventLoop().schedule(enableAutoReadTask, 1, TimeUnit.SECONDS);
289             }
290             // still let the exceptionCaught event flow through the pipeline to give the user
291             // a chance to do something with it
292             ctx.fireExceptionCaught(cause);
293         }
294     }
295 
296     @Override
297     @SuppressWarnings("CloneDoesntCallSuperClone")
298     public ServerBootstrap clone() {
299         return new ServerBootstrap(this);
300     }
301 
302     @Override
303     public String toString() {
304         StringBuilder buf = new StringBuilder(super.toString());
305         buf.setLength(buf.length() - 1);
306         buf.append(", ");
307         if (childGroup != null) {
308             buf.append("childGroup: ");
309             buf.append(StringUtil.simpleClassName(childGroup));
310             buf.append(", ");
311         }
312         synchronized (childOptions) {
313             if (!childOptions.isEmpty()) {
314                 buf.append("childOptions: ");
315                 buf.append(childOptions);
316                 buf.append(", ");
317             }
318         }
319         synchronized (childAttrs) {
320             if (!childAttrs.isEmpty()) {
321                 buf.append("childAttrs: ");
322                 buf.append(childAttrs);
323                 buf.append(", ");
324             }
325         }
326         if (childHandler != null) {
327             buf.append("childHandler: ");
328             buf.append(childHandler);
329             buf.append(", ");
330         }
331         if (buf.charAt(buf.length() - 1) == '(') {
332             buf.append(')');
333         } else {
334             buf.setCharAt(buf.length() - 2, ')');
335             buf.setLength(buf.length() - 1);
336         }
337 
338         return buf.toString();
339     }
340 }