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.ChannelHandler;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelInboundHandlerAdapter;
23  import io.netty.channel.ChannelInitializer;
24  import io.netty.channel.ChannelOption;
25  import io.netty.channel.ChannelPipeline;
26  import io.netty.channel.EventLoopGroup;
27  import io.netty.channel.ServerChannel;
28  import io.netty.util.AttributeKey;
29  import io.netty.util.internal.ObjectUtil;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  import java.util.Collection;
34  import java.util.LinkedHashMap;
35  import java.util.Map;
36  import java.util.Map.Entry;
37  import java.util.concurrent.ConcurrentHashMap;
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      // The order in which child ChannelOptions are applied is important they may depend on each other for validation
49      // purposes.
50      private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
51      private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
52      private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
53      private volatile EventLoopGroup childGroup;
54      private volatile ChannelHandler childHandler;
55  
56      public ServerBootstrap() { }
57  
58      private ServerBootstrap(ServerBootstrap bootstrap) {
59          super(bootstrap);
60          childGroup = bootstrap.childGroup;
61          childHandler = bootstrap.childHandler;
62          synchronized (bootstrap.childOptions) {
63              childOptions.putAll(bootstrap.childOptions);
64          }
65          childAttrs.putAll(bootstrap.childAttrs);
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       * <p>
81       * <strong>Important:</strong> Usually this is only useful for advanced use-cases and usually
82       * {@link #group(EventLoopGroup)} is the preferred way to configure the group.
83       */
84      public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
85          super.group(parentGroup);
86          if (this.childGroup != null) {
87              throw new IllegalStateException("childGroup set already");
88          }
89          this.childGroup = ObjectUtil.checkNotNull(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          ObjectUtil.checkNotNull(childOption, "childOption");
100         synchronized (childOptions) {
101             if (value == null) {
102                 childOptions.remove(childOption);
103             } else {
104                 childOptions.put(childOption, value);
105             }
106         }
107         return this;
108     }
109 
110     /**
111      * Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
112      * {@code null} the {@link AttributeKey} is removed
113      */
114     public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
115         ObjectUtil.checkNotNull(childKey, "childKey");
116         if (value == null) {
117             childAttrs.remove(childKey);
118         } else {
119             childAttrs.put(childKey, value);
120         }
121         return this;
122     }
123 
124     /**
125      * Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s.
126      */
127     public ServerBootstrap childHandler(ChannelHandler childHandler) {
128         this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
129         return this;
130     }
131 
132     @Override
133     void init(Channel channel) throws Throwable {
134         setChannelOptions(channel, newOptionsArray(), logger);
135         setAttributes(channel, newAttributesArray());
136 
137         ChannelPipeline p = channel.pipeline();
138 
139         final EventLoopGroup currentChildGroup = childGroup;
140         final ChannelHandler currentChildHandler = childHandler;
141         final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
142         final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
143         final Collection<ChannelInitializerExtension> extensions = getInitializerExtensions();
144 
145         p.addLast(new ChannelInitializer<Channel>() {
146             @Override
147             public void initChannel(final Channel ch) {
148                 final ChannelPipeline pipeline = ch.pipeline();
149                 ChannelHandler handler = config.handler();
150                 if (handler != null) {
151                     pipeline.addLast(handler);
152                 }
153 
154                 ch.eventLoop().execute(new Runnable() {
155                     @Override
156                     public void run() {
157                         pipeline.addLast(new ServerBootstrapAcceptor(
158                                 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs,
159                                 extensions));
160                     }
161                 });
162             }
163         });
164         if (!extensions.isEmpty() && channel instanceof ServerChannel) {
165             ServerChannel serverChannel = (ServerChannel) channel;
166             for (ChannelInitializerExtension extension : extensions) {
167                 try {
168                     extension.postInitializeServerListenerChannel(serverChannel);
169                 } catch (Exception e) {
170                     logger.warn("Exception thrown from postInitializeServerListenerChannel", e);
171                 }
172             }
173         }
174     }
175 
176     @Override
177     public ServerBootstrap validate() {
178         super.validate();
179         if (childHandler == null) {
180             throw new IllegalStateException("childHandler not set");
181         }
182         if (childGroup == null) {
183             logger.warn("childGroup is not set. Using parentGroup instead.");
184             childGroup = config.group();
185         }
186         return this;
187     }
188 
189     private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
190 
191         private final EventLoopGroup childGroup;
192         private final ChannelHandler childHandler;
193         private final Entry<ChannelOption<?>, Object>[] childOptions;
194         private final Entry<AttributeKey<?>, Object>[] childAttrs;
195         private final Runnable enableAutoReadTask;
196         private final Collection<ChannelInitializerExtension> extensions;
197 
198         ServerBootstrapAcceptor(
199                 final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
200                 Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs,
201                 Collection<ChannelInitializerExtension> extensions) {
202             this.childGroup = childGroup;
203             this.childHandler = childHandler;
204             this.childOptions = childOptions;
205             this.childAttrs = childAttrs;
206             this.extensions = extensions;
207 
208             // Task which is scheduled to re-enable auto-read.
209             // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
210             // not be able to load the class because of the file limit it already reached.
211             //
212             // See https://github.com/netty/netty/issues/1328
213             enableAutoReadTask = new Runnable() {
214                 @Override
215                 public void run() {
216                     channel.config().setAutoRead(true);
217                 }
218             };
219         }
220 
221         @Override
222         @SuppressWarnings("unchecked")
223         public void channelRead(ChannelHandlerContext ctx, Object msg) {
224             final Channel child = (Channel) msg;
225 
226             child.pipeline().addLast(childHandler);
227 
228             try {
229                 setChannelOptions(child, childOptions, logger);
230             } catch (Throwable cause) {
231                 forceClose(child, cause);
232                 return;
233             }
234             setAttributes(child, childAttrs);
235 
236             if (!extensions.isEmpty()) {
237                 for (ChannelInitializerExtension extension : extensions) {
238                     try {
239                         extension.postInitializeServerChildChannel(child);
240                     } catch (Exception e) {
241                         logger.warn("Exception thrown from postInitializeServerChildChannel", e);
242                     }
243                 }
244             }
245 
246             try {
247                 childGroup.register(child).addListener(future -> {
248                     if (!future.isSuccess()) {
249                         forceClose(child, future.cause());
250                     }
251                 });
252             } catch (Throwable t) {
253                 forceClose(child, t);
254             }
255         }
256 
257         private static void forceClose(Channel child, Throwable t) {
258             child.unsafe().closeForcibly();
259             logger.warn("Failed to register an accepted channel: {}", child, t);
260         }
261 
262         @Override
263         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
264             final ChannelConfig config = ctx.channel().config();
265             if (config.isAutoRead()) {
266                 // stop accept new connections for 1 second to allow the channel to recover
267                 // See https://github.com/netty/netty/issues/1328
268                 config.setAutoRead(false);
269                 ctx.channel().eventLoop().schedule(enableAutoReadTask, 1, TimeUnit.SECONDS);
270             }
271             // still let the exceptionCaught event flow through the pipeline to give the user
272             // a chance to do something with it
273             ctx.fireExceptionCaught(cause);
274         }
275     }
276 
277     @Override
278     @SuppressWarnings("CloneDoesntCallSuperClone")
279     public ServerBootstrap clone() {
280         return new ServerBootstrap(this);
281     }
282 
283     /**
284      * Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
285      * if non is configured yet.
286      *
287      * @deprecated Use {@link #config()} instead.
288      */
289     @Deprecated
290     public EventLoopGroup childGroup() {
291         return childGroup;
292     }
293 
294     final ChannelHandler childHandler() {
295         return childHandler;
296     }
297 
298     final Map<ChannelOption<?>, Object> childOptions() {
299         synchronized (childOptions) {
300             return copiedMap(childOptions);
301         }
302     }
303 
304     final Map<AttributeKey<?>, Object> childAttrs() {
305         return copiedMap(childAttrs);
306     }
307 
308     @Override
309     public final ServerBootstrapConfig config() {
310         return config;
311     }
312 }