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.ChannelFuture;
20  import io.netty.channel.ChannelFutureListener;
21  import io.netty.channel.ChannelPipeline;
22  import io.netty.channel.ChannelPromise;
23  import io.netty.channel.EventLoop;
24  import io.netty.channel.EventLoopGroup;
25  import io.netty.resolver.AddressResolver;
26  import io.netty.resolver.AddressResolverGroup;
27  import io.netty.resolver.DefaultAddressResolverGroup;
28  import io.netty.resolver.NameResolver;
29  import io.netty.util.concurrent.Future;
30  import io.netty.util.concurrent.FutureListener;
31  import io.netty.util.internal.ObjectUtil;
32  import io.netty.util.internal.logging.InternalLogger;
33  import io.netty.util.internal.logging.InternalLoggerFactory;
34  
35  import java.net.InetAddress;
36  import java.net.InetSocketAddress;
37  import java.net.SocketAddress;
38  import java.util.Collection;
39  
40  /**
41   * A {@link Bootstrap} that makes it easy to bootstrap a {@link Channel} to use
42   * for clients.
43   *
44   * <p>The {@link #bind()} methods are useful in combination with connectionless transports such as datagram (UDP).
45   * For regular TCP connections, please use the provided {@link #connect()} methods.</p>
46   */
47  public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
48  
49      private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
50  
51      private final BootstrapConfig config = new BootstrapConfig(this);
52  
53      private ExternalAddressResolver externalResolver;
54      private volatile boolean disableResolver;
55      private volatile SocketAddress remoteAddress;
56  
57      public Bootstrap() { }
58  
59      private Bootstrap(Bootstrap bootstrap) {
60          super(bootstrap);
61          externalResolver = bootstrap.externalResolver;
62          disableResolver = bootstrap.disableResolver;
63          remoteAddress = bootstrap.remoteAddress;
64      }
65  
66      /**
67       * Sets the {@link NameResolver} which will resolve the address of the unresolved named address.
68       *
69       * @param resolver the {@link NameResolver} for this {@code Bootstrap}; may be {@code null}, in which case a default
70       *                 resolver will be used
71       *
72       * @see io.netty.resolver.DefaultAddressResolverGroup
73       */
74      public Bootstrap resolver(AddressResolverGroup<?> resolver) {
75          externalResolver = resolver == null ? null : new ExternalAddressResolver(resolver);
76          disableResolver = false;
77          return this;
78      }
79  
80      /**
81       * Disables address name resolution. Name resolution may be re-enabled with
82       * {@link Bootstrap#resolver(AddressResolverGroup)}
83       */
84      public Bootstrap disableResolver() {
85          externalResolver = null;
86          disableResolver = true;
87          return this;
88      }
89  
90      /**
91       * The {@link SocketAddress} to connect to once the {@link #connect()} method
92       * is called.
93       */
94      public Bootstrap remoteAddress(SocketAddress remoteAddress) {
95          this.remoteAddress = remoteAddress;
96          return this;
97      }
98  
99      /**
100      * @see #remoteAddress(SocketAddress)
101      */
102     public Bootstrap remoteAddress(String inetHost, int inetPort) {
103         remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
104         return this;
105     }
106 
107     /**
108      * @see #remoteAddress(SocketAddress)
109      */
110     public Bootstrap remoteAddress(InetAddress inetHost, int inetPort) {
111         remoteAddress = new InetSocketAddress(inetHost, inetPort);
112         return this;
113     }
114 
115     /**
116      * Connect a {@link Channel} to the remote peer.
117      */
118     public ChannelFuture connect() {
119         validate();
120         SocketAddress remoteAddress = this.remoteAddress;
121         if (remoteAddress == null) {
122             throw new IllegalStateException("remoteAddress not set");
123         }
124 
125         return doResolveAndConnect(remoteAddress, config.localAddress());
126     }
127 
128     /**
129      * Connect a {@link Channel} to the remote peer.
130      */
131     public ChannelFuture connect(String inetHost, int inetPort) {
132         return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
133     }
134 
135     /**
136      * Connect a {@link Channel} to the remote peer.
137      */
138     public ChannelFuture connect(InetAddress inetHost, int inetPort) {
139         return connect(new InetSocketAddress(inetHost, inetPort));
140     }
141 
142     /**
143      * Connect a {@link Channel} to the remote peer.
144      */
145     public ChannelFuture connect(SocketAddress remoteAddress) {
146         ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
147         validate();
148         return doResolveAndConnect(remoteAddress, config.localAddress());
149     }
150 
151     /**
152      * Connect a {@link Channel} to the remote peer.
153      */
154     public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
155         ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
156         validate();
157         return doResolveAndConnect(remoteAddress, localAddress);
158     }
159 
160     /**
161      * @see #connect()
162      */
163     private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
164         final ChannelFuture regFuture = initAndRegister();
165         final Channel channel = regFuture.channel();
166 
167         if (regFuture.isDone()) {
168             if (!regFuture.isSuccess()) {
169                 return regFuture;
170             }
171             return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
172         } else {
173             // Registration future is almost always fulfilled already, but just in case it's not.
174             final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
175             regFuture.addListener(new ChannelFutureListener() {
176                 @Override
177                 public void operationComplete(ChannelFuture future) throws Exception {
178                     // Directly obtain the cause and do a null check so we only need one volatile read in case of a
179                     // failure.
180                     Throwable cause = future.cause();
181                     if (cause != null) {
182                         // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
183                         // IllegalStateException once we try to access the EventLoop of the Channel.
184                         promise.setFailure(cause);
185                     } else {
186                         // Registration was successful, so set the correct executor to use.
187                         // See https://github.com/netty/netty/issues/2586
188                         promise.registered();
189                         doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
190                     }
191                 }
192             });
193             return promise;
194         }
195     }
196 
197     private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
198                                                final SocketAddress localAddress, final ChannelPromise promise) {
199         // The Channel was created and registered before the address resolution started. Close it if the connect
200         // attempt fails or is cancelled while resolution is still in progress.
201         promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
202         if (promise.isDone()) {
203             return promise;
204         }
205 
206         try {
207             if (disableResolver) {
208                 doConnect(remoteAddress, localAddress, promise);
209                 return promise;
210             }
211 
212             final EventLoop eventLoop = channel.eventLoop();
213             AddressResolver<SocketAddress> resolver;
214             try {
215                 resolver = ExternalAddressResolver.getOrDefault(externalResolver).getResolver(eventLoop);
216             } catch (Throwable cause) {
217                 channel.close();
218                 promise.tryFailure(cause);
219                 return promise;
220             }
221 
222             if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
223                 // Resolver has no idea about what to do with the specified remote address or it's resolved already.
224                 doConnect(remoteAddress, localAddress, promise);
225                 return promise;
226             }
227 
228             final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
229 
230             if (resolveFuture.isDone()) {
231                 final Throwable resolveFailureCause = resolveFuture.cause();
232 
233                 if (resolveFailureCause != null) {
234                     // Failed to resolve immediately
235                     channel.close();
236                     promise.tryFailure(resolveFailureCause);
237                 } else {
238                     // Succeeded to resolve immediately; cached? (or did a blocking lookup)
239                     doConnect(resolveFuture.getNow(), localAddress, promise);
240                 }
241                 return promise;
242             }
243 
244             // Wait until the name resolution is finished.
245             resolveFuture.addListener(new FutureListener<SocketAddress>() {
246                 @Override
247                 public void operationComplete(Future<SocketAddress> future) throws Exception {
248                     Throwable cause = future.cause();
249                     if (cause != null) {
250                         channel.close();
251                         promise.tryFailure(cause);
252                     } else if (!promise.isDone()) {
253                         doConnect(future.getNow(), localAddress, promise);
254                     }
255                 }
256             });
257         } catch (Throwable cause) {
258             promise.tryFailure(cause);
259         }
260         return promise;
261     }
262 
263     private static void doConnect(
264             final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
265 
266         // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
267         // the pipeline in its channelRegistered() implementation.
268         final Channel channel = connectPromise.channel();
269         channel.eventLoop().execute(new Runnable() {
270             @Override
271             public void run() {
272                 if (localAddress == null) {
273                     channel.connect(remoteAddress, connectPromise);
274                 } else {
275                     channel.connect(remoteAddress, localAddress, connectPromise);
276                 }
277             }
278         });
279     }
280 
281     @Override
282     void init(Channel channel) throws Throwable {
283         ChannelPipeline p = channel.pipeline();
284         p.addLast(config.handler());
285 
286         setChannelOptions(channel, newOptionsArray(), logger);
287 
288         setAttributes(channel, newAttributesArray());
289         Collection<ChannelInitializerExtension> extensions = getInitializerExtensions();
290         if (!extensions.isEmpty()) {
291             for (ChannelInitializerExtension extension : extensions) {
292                 try {
293                     extension.postInitializeClientChannel(channel);
294                 } catch (Exception e) {
295                     logger.warn("Exception thrown from postInitializeClientChannel", e);
296                 }
297             }
298         }
299     }
300 
301     @Override
302     public Bootstrap validate() {
303         super.validate();
304         if (config.handler() == null) {
305             throw new IllegalStateException("handler not set");
306         }
307         return this;
308     }
309 
310     @Override
311     @SuppressWarnings("CloneDoesntCallSuperClone")
312     public Bootstrap clone() {
313         return new Bootstrap(this);
314     }
315 
316     /**
317      * Returns a deep clone of this bootstrap which has the identical configuration except that it uses
318      * the given {@link EventLoopGroup}. This method is useful when making multiple {@link Channel}s with similar
319      * settings.
320      */
321     public Bootstrap clone(EventLoopGroup group) {
322         Bootstrap bs = new Bootstrap(this);
323         bs.group = group;
324         return bs;
325     }
326 
327     @Override
328     public final BootstrapConfig config() {
329         return config;
330     }
331 
332     final SocketAddress remoteAddress() {
333         return remoteAddress;
334     }
335 
336     final AddressResolverGroup<?> resolver() {
337         if (disableResolver) {
338             return null;
339         }
340         return ExternalAddressResolver.getOrDefault(externalResolver);
341     }
342 
343     /* Holder to avoid NoClassDefFoundError in case netty-resolver dependency is excluded
344        (e.g. some address families do not need name resolution) */
345     static final class ExternalAddressResolver {
346         final AddressResolverGroup<SocketAddress> resolverGroup;
347 
348         @SuppressWarnings("unchecked")
349         ExternalAddressResolver(AddressResolverGroup<?> resolverGroup) {
350             this.resolverGroup = (AddressResolverGroup<SocketAddress>) resolverGroup;
351         }
352 
353         @SuppressWarnings("unchecked")
354         static AddressResolverGroup<SocketAddress> getOrDefault(ExternalAddressResolver externalResolver) {
355             if (externalResolver == null) {
356                 AddressResolverGroup<?> defaultResolverGroup = DefaultAddressResolverGroup.INSTANCE;
357                 return (AddressResolverGroup<SocketAddress>) defaultResolverGroup;
358             }
359             return externalResolver.resolverGroup;
360         }
361     }
362 }