View Javadoc

1   /*
2    * Copyright 2009 Red Hat, Inc.
3    *
4    * Red Hat licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * 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 org.jboss.netty.bootstrap;
17  
18  import static org.jboss.netty.channel.Channels.*;
19  
20  import java.net.InetSocketAddress;
21  import java.net.SocketAddress;
22  import java.util.HashMap;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import java.util.concurrent.BlockingQueue;
26  import java.util.concurrent.LinkedBlockingQueue;
27  import java.util.concurrent.TimeUnit;
28  
29  import org.jboss.netty.channel.Channel;
30  import org.jboss.netty.channel.ChannelConfig;
31  import org.jboss.netty.channel.ChannelException;
32  import org.jboss.netty.channel.ChannelFactory;
33  import org.jboss.netty.channel.ChannelFuture;
34  import org.jboss.netty.channel.ChannelHandler;
35  import org.jboss.netty.channel.ChannelHandlerContext;
36  import org.jboss.netty.channel.ChannelPipeline;
37  import org.jboss.netty.channel.ChannelPipelineFactory;
38  import org.jboss.netty.channel.ChannelStateEvent;
39  import org.jboss.netty.channel.Channels;
40  import org.jboss.netty.channel.ChildChannelStateEvent;
41  import org.jboss.netty.channel.ExceptionEvent;
42  import org.jboss.netty.channel.ServerChannelFactory;
43  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
44  
45  /**
46   * A helper class which creates a new server-side {@link Channel} and accepts
47   * incoming connections.
48   *
49   * <h3>Only for connection oriented transports</h3>
50   *
51   * This bootstrap is for connection oriented transports only such as TCP/IP
52   * and local transport.  Use {@link ConnectionlessBootstrap} instead for
53   * connectionless transports.  Do not use this helper if you are using a
54   * connectionless transport such as UDP/IP which does not accept an incoming
55   * connection but receives messages by itself without creating a child channel.
56   *
57   * <h3>Parent channel and its children</h3>
58   *
59   * A parent channel is a channel which is supposed to accept incoming
60   * connections.  It is created by this bootstrap's {@link ChannelFactory} via
61   * {@link #bind()} and {@link #bind(SocketAddress)}.
62   * <p>
63   * Once successfully bound, the parent channel starts to accept incoming
64   * connections, and the accepted connections become the children of the
65   * parent channel.
66   *
67   * <h3>Configuring channels</h3>
68   *
69   * {@link #setOption(String, Object) Options} are used to configure both a
70   * parent channel and its child channels.  To configure the child channels,
71   * prepend {@code "child."} prefix to the actual option names of a child
72   * channel:
73   *
74   * <pre>
75   * {@link ServerBootstrap} b = ...;
76   *
77   * // Options for a parent channel
78   * b.setOption("localAddress", new {@link InetSocketAddress}(8080));
79   * b.setOption("reuseAddress", true);
80   *
81   * // Options for its children
82   * b.setOption("child.tcpNoDelay", true);
83   * b.setOption("child.receiveBufferSize", 1048576);
84   * </pre>
85   *
86   * For the detailed list of available options, please refer to
87   * {@link ChannelConfig} and its sub-types.
88   *
89   * <h3>Configuring a parent channel pipeline</h3>
90   *
91   * It is rare to customize the pipeline of a parent channel because what it is
92   * supposed to do is very typical.  However, you might want to add a handler
93   * to deal with some special needs such as degrading the process
94   * <a href="http://en.wikipedia.org/wiki/User_identifier_(Unix)">UID</a> from
95   * a <a href="http://en.wikipedia.org/wiki/Superuser">superuser</a> to a
96   * normal user and changing the current VM security manager for better
97   * security.  To support such a case,
98   * the {@link #setParentHandler(ChannelHandler) parentHandler} property is
99   * provided.
100  *
101  * <h3>Configuring a child channel pipeline</h3>
102  *
103  * Every channel has its own {@link ChannelPipeline} and you can configure it
104  * in two ways.
105  *
106  * The recommended approach is to specify a {@link ChannelPipelineFactory} by
107  * calling {@link #setPipelineFactory(ChannelPipelineFactory)}.
108  *
109  * <pre>
110  * {@link ServerBootstrap} b = ...;
111  * b.setPipelineFactory(new MyPipelineFactory());
112  *
113  * public class MyPipelineFactory implements {@link ChannelPipelineFactory} {
114  *   public {@link ChannelPipeline} getPipeline() throws Exception {
115  *     // Create and configure a new pipeline for a new channel.
116  *     {@link ChannelPipeline} p = {@link Channels}.pipeline();
117  *     p.addLast("encoder", new EncodingHandler());
118  *     p.addLast("decoder", new DecodingHandler());
119  *     p.addLast("logic",   new LogicHandler());
120  *     return p;
121  *   }
122  * }
123  * </pre>
124 
125  * <p>
126  * The alternative approach, which works only in a certain situation, is to use
127  * the default pipeline and let the bootstrap to shallow-copy the default
128  * pipeline for each new channel:
129  *
130  * <pre>
131  * {@link ServerBootstrap} b = ...;
132  * {@link ChannelPipeline} p = b.getPipeline();
133  *
134  * // Add handlers to the default pipeline.
135  * p.addLast("encoder", new EncodingHandler());
136  * p.addLast("decoder", new DecodingHandler());
137  * p.addLast("logic",   new LogicHandler());
138  * </pre>
139  *
140  * Please note 'shallow-copy' here means that the added {@link ChannelHandler}s
141  * are not cloned but only their references are added to the new pipeline.
142  * Therefore, you cannot use this approach if you are going to open more than
143  * one {@link Channel}s or run a server that accepts incoming connections to
144  * create its child channels.
145  *
146  * <h3>Applying different settings for different {@link Channel}s</h3>
147  *
148  * {@link ServerBootstrap} is just a helper class.  It neither allocates nor
149  * manages any resources.  What manages the resources is the
150  * {@link ChannelFactory} implementation you specified in the constructor of
151  * {@link ServerBootstrap}.  Therefore, it is OK to create as many
152  * {@link ServerBootstrap} instances as you want with the same
153  * {@link ChannelFactory} to apply different settings for different
154  * {@link Channel}s.
155  *
156  * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
157  * @author <a href="http://gleamynode.net/">Trustin Lee</a>
158  *
159  * @version $Rev: 2344 $, $Date: 2010-07-07 16:55:37 +0900 (Wed, 07 Jul 2010) $
160  *
161  * @apiviz.landmark
162  */
163 public class ServerBootstrap extends Bootstrap {
164 
165     private volatile ChannelHandler parentHandler;
166 
167     /**
168      * Creates a new instance with no {@link ChannelFactory} set.
169      * {@link #setFactory(ChannelFactory)} must be called before any I/O
170      * operation is requested.
171      */
172     public ServerBootstrap() {
173         super();
174     }
175 
176     /**
177      * Creates a new instance with the specified initial {@link ChannelFactory}.
178      */
179     public ServerBootstrap(ChannelFactory channelFactory) {
180         super(channelFactory);
181     }
182 
183     /**
184      * {@inheritDoc}
185      *
186      * @throws IllegalArgumentException
187      *         if the specified {@code factory} is not a
188      *         {@link ServerChannelFactory}
189      */
190     @Override
191     public void setFactory(ChannelFactory factory) {
192         if (factory == null) {
193             throw new NullPointerException("factory");
194         }
195         if (!(factory instanceof ServerChannelFactory)) {
196             throw new IllegalArgumentException(
197                     "factory must be a " +
198                     ServerChannelFactory.class.getSimpleName() + ": " +
199                     factory.getClass());
200         }
201         super.setFactory(factory);
202     }
203 
204     /**
205      * Returns an optional {@link ChannelHandler} which intercepts an event
206      * of a newly bound server-side channel which accepts incoming connections.
207      *
208      * @return the parent channel handler.
209      *         {@code null} if no parent channel handler is set.
210      */
211     public ChannelHandler getParentHandler() {
212         return parentHandler;
213     }
214 
215     /**
216      * Sets an optional {@link ChannelHandler} which intercepts an event of
217      * a newly bound server-side channel which accepts incoming connections.
218      *
219      * @param parentHandler
220      *        the parent channel handler.
221      *        {@code null} to unset the current parent channel handler.
222      */
223     public void setParentHandler(ChannelHandler parentHandler) {
224         this.parentHandler = parentHandler;
225     }
226 
227     /**
228      * Creates a new channel which is bound to the local address which was
229      * specified in the current {@code "localAddress"} option.  This method is
230      * similar to the following code:
231      *
232      * <pre>
233      * {@link ServerBootstrap} b = ...;
234      * b.bind(b.getOption("localAddress"));
235      * </pre>
236      *
237      * @return a new bound channel which accepts incoming connections
238      *
239      * @throws IllegalStateException
240      *         if {@code "localAddress"} option was not set
241      * @throws ClassCastException
242      *         if {@code "localAddress"} option's value is
243      *         neither a {@link SocketAddress} nor {@code null}
244      * @throws ChannelException
245      *         if failed to create a new channel and
246      *                      bind it to the local address
247      */
248     public Channel bind() {
249         SocketAddress localAddress = (SocketAddress) getOption("localAddress");
250         if (localAddress == null) {
251             throw new IllegalStateException("localAddress option is not set.");
252         }
253         return bind(localAddress);
254     }
255 
256     /**
257      * Creates a new channel which is bound to the specified local address.
258      *
259      * @return a new bound channel which accepts incoming connections
260      *
261      * @throws ChannelException
262      *         if failed to create a new channel and
263      *                      bind it to the local address
264      */
265     public Channel bind(final SocketAddress localAddress) {
266         if (localAddress == null) {
267             throw new NullPointerException("localAddress");
268         }
269 
270         final BlockingQueue<ChannelFuture> futureQueue =
271             new LinkedBlockingQueue<ChannelFuture>();
272 
273         ChannelHandler binder = new Binder(localAddress, futureQueue);
274         ChannelHandler parentHandler = getParentHandler();
275 
276         ChannelPipeline bossPipeline = pipeline();
277         bossPipeline.addLast("binder", binder);
278         if (parentHandler != null) {
279             bossPipeline.addLast("userHandler", parentHandler);
280         }
281 
282         Channel channel = getFactory().newChannel(bossPipeline);
283 
284         // Wait until the future is available.
285         ChannelFuture future = null;
286         boolean interrupted = false;
287         do {
288             try {
289                 future = futureQueue.poll(Integer.MAX_VALUE, TimeUnit.SECONDS);
290             } catch (InterruptedException e) {
291                 interrupted = true;
292             }
293         } while (future == null);
294 
295         if (interrupted) {
296             Thread.currentThread().interrupt();
297         }
298 
299         // Wait for the future.
300         future.awaitUninterruptibly();
301         if (!future.isSuccess()) {
302             future.getChannel().close().awaitUninterruptibly();
303             throw new ChannelException("Failed to bind to: " + localAddress, future.getCause());
304         }
305 
306         return channel;
307     }
308 
309     private final class Binder extends SimpleChannelUpstreamHandler {
310 
311         private final SocketAddress localAddress;
312         private final BlockingQueue<ChannelFuture> futureQueue;
313         private final Map<String, Object> childOptions =
314             new HashMap<String, Object>();
315 
316         Binder(SocketAddress localAddress, BlockingQueue<ChannelFuture> futureQueue) {
317             this.localAddress = localAddress;
318             this.futureQueue = futureQueue;
319         }
320 
321         @Override
322         public void channelOpen(
323                 ChannelHandlerContext ctx,
324                 ChannelStateEvent evt) {
325 
326             try {
327                 evt.getChannel().getConfig().setPipelineFactory(getPipelineFactory());
328 
329                 // Split options into two categories: parent and child.
330                 Map<String, Object> allOptions = getOptions();
331                 Map<String, Object> parentOptions = new HashMap<String, Object>();
332                 for (Entry<String, Object> e: allOptions.entrySet()) {
333                     if (e.getKey().startsWith("child.")) {
334                         childOptions.put(
335                                 e.getKey().substring(6),
336                                 e.getValue());
337                     } else if (!e.getKey().equals("pipelineFactory")) {
338                         parentOptions.put(e.getKey(), e.getValue());
339                     }
340                 }
341 
342                 // Apply parent options.
343                 evt.getChannel().getConfig().setOptions(parentOptions);
344             } finally {
345                 ctx.sendUpstream(evt);
346             }
347 
348             boolean finished = futureQueue.offer(evt.getChannel().bind(localAddress));
349             assert finished;
350         }
351 
352         @Override
353         public void childChannelOpen(
354                 ChannelHandlerContext ctx,
355                 ChildChannelStateEvent e) throws Exception {
356             // Apply child options.
357             e.getChildChannel().getConfig().setOptions(childOptions);
358             ctx.sendUpstream(e);
359         }
360 
361         @Override
362         public void exceptionCaught(
363                 ChannelHandlerContext ctx, ExceptionEvent e)
364                 throws Exception {
365             boolean finished = futureQueue.offer(failedFuture(e.getChannel(), e.getCause()));
366             assert finished;
367             ctx.sendUpstream(e);
368         }
369     }
370 }