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