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.ChannelHandlerAdapter;
24  import io.netty.channel.ChannelHandlerContext;
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.channel.socket.SocketChannel;
31  import io.netty.util.AttributeKey;
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   * {@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      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 {@link 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 {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
78       * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
79       * {@link 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 {@link ChannelOption} which is used for the {@link Channel} instances once they get created
95       * (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set
96       * {@link 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 {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
116      * {@code null} the {@link 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 {@link ChannelHandler} which is used to serve the request for the {@link 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 {@link EventLoopGroup} which will be used for the child channels or {@code 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         if (handler() != null) {
167             p.addLast(handler());
168         }
169 
170         final EventLoopGroup currentChildGroup = childGroup;
171         final ChannelHandler currentChildHandler = childHandler;
172         final Entry<ChannelOption<?>, Object>[] currentChildOptions;
173         final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
174         synchronized (childOptions) {
175             currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
176         }
177         synchronized (childAttrs) {
178             currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
179         }
180 
181         p.addLast(new ChannelInitializer<Channel>() {
182             @Override
183             public void initChannel(Channel ch) throws Exception {
184                 ch.pipeline().addLast(new ServerBootstrapAcceptor(
185                         currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
186             }
187         });
188     }
189 
190     @Override
191     public ServerBootstrap validate() {
192         super.validate();
193         if (childHandler == null) {
194             throw new IllegalStateException("childHandler not set");
195         }
196         if (childGroup == null) {
197             logger.warn("childGroup is not set. Using parentGroup instead.");
198             childGroup = group();
199         }
200         return this;
201     }
202 
203     @SuppressWarnings("unchecked")
204     private static Entry<ChannelOption<?>, Object>[] newOptionArray(int size) {
205         return new Entry[size];
206     }
207 
208     @SuppressWarnings("unchecked")
209     private static Entry<AttributeKey<?>, Object>[] newAttrArray(int size) {
210         return new Entry[size];
211     }
212 
213     private static class ServerBootstrapAcceptor extends ChannelHandlerAdapter {
214 
215         private final EventLoopGroup childGroup;
216         private final ChannelHandler childHandler;
217         private final Entry<ChannelOption<?>, Object>[] childOptions;
218         private final Entry<AttributeKey<?>, Object>[] childAttrs;
219 
220         ServerBootstrapAcceptor(
221                 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 
229         @Override
230         @SuppressWarnings("unchecked")
231         public void channelRead(ChannelHandlerContext ctx, Object msg) {
232             final Channel child = (Channel) msg;
233 
234             child.pipeline().addLast(childHandler);
235 
236             for (Entry<ChannelOption<?>, Object> e: childOptions) {
237                 try {
238                     if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
239                         logger.warn("Unknown channel option: " + e);
240                     }
241                 } catch (Throwable t) {
242                     logger.warn("Failed to set a channel option: " + child, t);
243                 }
244             }
245 
246             for (Entry<AttributeKey<?>, Object> e: childAttrs) {
247                 child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
248             }
249 
250             try {
251                 childGroup.register(child).addListener(new ChannelFutureListener() {
252                     @Override
253                     public void operationComplete(ChannelFuture future) throws Exception {
254                         if (!future.isSuccess()) {
255                             forceClose(child, future.cause());
256                         }
257                     }
258                 });
259             } catch (Throwable t) {
260                 forceClose(child, t);
261             }
262         }
263 
264         private static void forceClose(Channel child, Throwable t) {
265             child.unsafe().closeForcibly();
266             logger.warn("Failed to register an accepted channel: " + child, t);
267         }
268 
269         @Override
270         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
271             final ChannelConfig config = ctx.channel().config();
272             if (config.isAutoRead()) {
273                 // stop accept new connections for 1 second to allow the channel to recover
274                 // See https://github.com/netty/netty/issues/1328
275                 config.setAutoRead(false);
276                 ctx.channel().eventLoop().schedule(new Runnable() {
277                     @Override
278                     public void run() {
279                         config.setAutoRead(true);
280                     }
281                 }, 1, TimeUnit.SECONDS);
282             }
283             // still let the exceptionCaught event flow through the pipeline to give the user
284             // a chance to do something with it
285             ctx.fireExceptionCaught(cause);
286         }
287     }
288 
289     @Override
290     @SuppressWarnings("CloneDoesntCallSuperClone")
291     public ServerBootstrap clone() {
292         return new ServerBootstrap(this);
293     }
294 
295     @Override
296     public String toString() {
297         StringBuilder buf = new StringBuilder(super.toString());
298         buf.setLength(buf.length() - 1);
299         buf.append(", ");
300         if (childGroup != null) {
301             buf.append("childGroup: ");
302             buf.append(StringUtil.simpleClassName(childGroup));
303             buf.append(", ");
304         }
305         synchronized (childOptions) {
306             if (!childOptions.isEmpty()) {
307                 buf.append("childOptions: ");
308                 buf.append(childOptions);
309                 buf.append(", ");
310             }
311         }
312         synchronized (childAttrs) {
313             if (!childAttrs.isEmpty()) {
314                 buf.append("childAttrs: ");
315                 buf.append(childAttrs);
316                 buf.append(", ");
317             }
318         }
319         if (childHandler != null) {
320             buf.append("childHandler: ");
321             buf.append(childHandler);
322             buf.append(", ");
323         }
324         if (buf.charAt(buf.length() - 1) == '(') {
325             buf.append(')');
326         } else {
327             buf.setCharAt(buf.length() - 2, ')');
328             buf.setLength(buf.length() - 1);
329         }
330 
331         return buf.toString();
332     }
333 }
334