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.netty5.bootstrap;
17  
18  import io.netty5.channel.Channel;
19  import io.netty5.channel.ChannelFactory;
20  import io.netty5.channel.ChannelFutureListeners;
21  import io.netty5.channel.ChannelPipeline;
22  import io.netty5.channel.EventLoop;
23  import io.netty5.channel.EventLoopGroup;
24  import io.netty5.channel.ReflectiveChannelFactory;
25  import io.netty5.resolver.AddressResolver;
26  import io.netty5.resolver.AddressResolverGroup;
27  import io.netty5.resolver.DefaultAddressResolverGroup;
28  import io.netty5.resolver.NameResolver;
29  import io.netty5.util.concurrent.Future;
30  import io.netty5.util.concurrent.Promise;
31  import io.netty5.util.internal.logging.InternalLogger;
32  import io.netty5.util.internal.logging.InternalLoggerFactory;
33  
34  import java.net.InetAddress;
35  import java.net.InetSocketAddress;
36  import java.net.SocketAddress;
37  
38  import static java.util.Objects.requireNonNull;
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, ChannelFactory<? extends Channel>> {
48  
49      private static final InternalLogger logger = InternalLoggerFactory.getInstance(Bootstrap.class);
50  
51      private static final AddressResolverGroup<?> DEFAULT_RESOLVER = DefaultAddressResolverGroup.INSTANCE;
52  
53      private final BootstrapConfig config = new BootstrapConfig(this);
54  
55      @SuppressWarnings("unchecked")
56      private volatile AddressResolverGroup<SocketAddress> resolver =
57              (AddressResolverGroup<SocketAddress>) DEFAULT_RESOLVER;
58      private volatile SocketAddress remoteAddress;
59      volatile ChannelFactory<? extends Channel> channelFactory;
60  
61      public Bootstrap() { }
62  
63      private Bootstrap(Bootstrap bootstrap) {
64          super(bootstrap);
65          resolver = bootstrap.resolver;
66          remoteAddress = bootstrap.remoteAddress;
67          channelFactory = bootstrap.channelFactory;
68      }
69  
70      /**
71       * Sets the {@link NameResolver} which will resolve the address of the unresolved named address.
72       *
73       * @param resolver the {@link NameResolver} for this {@code Bootstrap}; may be {@code null}, in which case a default
74       *                 resolver will be used
75       *
76       * @see DefaultAddressResolverGroup
77       */
78      @SuppressWarnings("unchecked")
79      public Bootstrap resolver(AddressResolverGroup<?> resolver) {
80          this.resolver = (AddressResolverGroup<SocketAddress>) (resolver == null ? DEFAULT_RESOLVER : resolver);
81          return this;
82      }
83  
84      /**
85       * The {@link SocketAddress} to connect to once the {@link #connect()} method
86       * is called.
87       */
88      public Bootstrap remoteAddress(SocketAddress remoteAddress) {
89          this.remoteAddress = remoteAddress;
90          return this;
91      }
92  
93      /**
94       * @see #remoteAddress(SocketAddress)
95       */
96      public Bootstrap remoteAddress(String inetHost, int inetPort) {
97          remoteAddress = InetSocketAddress.createUnresolved(inetHost, inetPort);
98          return this;
99      }
100 
101     /**
102      * @see #remoteAddress(SocketAddress)
103      */
104     public Bootstrap remoteAddress(InetAddress inetHost, int inetPort) {
105         remoteAddress = new InetSocketAddress(inetHost, inetPort);
106         return this;
107     }
108 
109     /**
110      * The {@link Class} which is used to create {@link Channel} instances from.
111      * You either use this or {@link #channelFactory(ChannelFactory)} if your
112      * {@link Channel} implementation has no no-args constructor.
113      */
114     public Bootstrap channel(Class<? extends Channel> channelClass) {
115         requireNonNull(channelClass, "channelClass");
116         return channelFactory(new ReflectiveChannelFactory<Channel>(channelClass));
117     }
118 
119     /**
120      * {@link ChannelFactory} which is used to create {@link Channel} instances from
121      * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)}
122      * is not working for you because of some more complex needs. If your {@link Channel} implementation
123      * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} to
124      * simplify your code.
125      */
126     public Bootstrap channelFactory(ChannelFactory<? extends Channel> channelFactory) {
127         requireNonNull(channelFactory, "channelFactory");
128         if (this.channelFactory != null) {
129             throw new IllegalStateException("channelFactory set already");
130         }
131 
132         this.channelFactory = channelFactory;
133         return this;
134     }
135 
136     /**
137      * Connect a {@link Channel} to the remote peer.
138      */
139     public Future<Channel> connect() {
140         validate();
141         SocketAddress remoteAddress = this.remoteAddress;
142         if (remoteAddress == null) {
143             throw new IllegalStateException("remoteAddress not set");
144         }
145 
146         return doResolveAndConnect(remoteAddress, config.localAddress());
147     }
148 
149     /**
150      * Connect a {@link Channel} to the remote peer.
151      */
152     public Future<Channel> connect(String inetHost, int inetPort) {
153         return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
154     }
155 
156     /**
157      * Connect a {@link Channel} to the remote peer.
158      */
159     public Future<Channel> connect(InetAddress inetHost, int inetPort) {
160         return connect(new InetSocketAddress(inetHost, inetPort));
161     }
162 
163     /**
164      * Connect a {@link Channel} to the remote peer.
165      */
166     public Future<Channel> connect(SocketAddress remoteAddress) {
167         requireNonNull(remoteAddress, "remoteAddress");
168 
169         validate();
170         return doResolveAndConnect(remoteAddress, config.localAddress());
171     }
172 
173     /**
174      * Connect a {@link Channel} to the remote peer.
175      */
176     public Future<Channel> connect(SocketAddress remoteAddress, SocketAddress localAddress) {
177         requireNonNull(remoteAddress, "remoteAddress");
178         validate();
179         return doResolveAndConnect(remoteAddress, localAddress);
180     }
181 
182     /**
183      * @see #connect()
184      */
185     private Future<Channel> doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
186         EventLoop loop = group.next();
187         final Future<Channel> regFuture = initAndRegister(loop);
188 
189         Promise<Channel> resolveAndConnectPromise = loop.newPromise();
190         if (regFuture.isDone()) {
191             if (regFuture.isFailed()) {
192                 return regFuture;
193             }
194             Channel channel = regFuture.getNow();
195             doResolveAndConnect0(channel, remoteAddress, localAddress, resolveAndConnectPromise);
196         } else {
197             // Registration future is almost always fulfilled already, but just in case it's not.
198             regFuture.addListener(future -> {
199                 // Directly obtain the cause and do a null check, so we only need one volatile read in case of a
200                 // failure.
201                 Throwable cause = future.cause();
202                 if (cause != null) {
203                     // Registration on the EventLoop failed so fail the Promise directly to not cause an
204                     // IllegalStateException once we try to access the EventLoop of the Channel.
205                     resolveAndConnectPromise.setFailure(cause);
206                 } else {
207                     Channel channel = future.getNow();
208                     doResolveAndConnect0(channel, remoteAddress, localAddress, resolveAndConnectPromise);
209                 }
210             });
211         }
212         return resolveAndConnectPromise.asFuture();
213     }
214 
215     private void doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
216                                       final SocketAddress localAddress, final Promise<Channel> promise) {
217         try {
218             final EventLoop eventLoop = channel.executor();
219             final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
220 
221             if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
222                 // Resolver has no idea about what to do with the specified remote address, or it's resolved already.
223                 doConnect(remoteAddress, localAddress, channel, promise);
224                 return;
225             }
226 
227             final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
228 
229             if (resolveFuture.isDone()) {
230                 final Throwable resolveFailureCause = resolveFuture.cause();
231 
232                 if (resolveFailureCause != null) {
233                     // Failed to resolve immediately
234                     channel.close();
235                     promise.setFailure(resolveFailureCause);
236                 } else {
237                     // Succeeded to resolve immediately; cached? (or did a blocking lookup)
238                     doConnect(resolveFuture.getNow(), localAddress, channel, promise);
239                     return;
240                 }
241             }
242 
243             // Wait until the name resolution is finished.
244             resolveFuture.addListener(future -> {
245                 if (future.cause() != null) {
246                     channel.close();
247                     promise.setFailure(future.cause());
248                 } else {
249                     doConnect(future.getNow(), localAddress, channel, promise);
250                 }
251             });
252         } catch (Throwable cause) {
253             promise.tryFailure(cause);
254         }
255     }
256 
257     private static void doConnect(
258             SocketAddress remoteAddress, SocketAddress localAddress, Channel channel, Promise<Channel> promise) {
259         // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
260         // the pipeline in its channelRegistered() implementation.
261         channel.executor().execute(() -> {
262             final Future<Void> future;
263             if (localAddress == null) {
264                 future = channel.connect(remoteAddress);
265             } else {
266                 future = channel.connect(remoteAddress, localAddress);
267             }
268             future.addListener(channel, ChannelFutureListeners.CLOSE_ON_FAILURE);
269             future.map(v -> channel).cascadeTo(promise);
270         });
271     }
272 
273     @Override
274     Future<Channel> init(Channel channel) {
275         ChannelPipeline p = channel.pipeline();
276 
277         setChannelOptions(channel, newOptionsArray(), logger);
278         setAttributes(channel, newAttributesArray());
279 
280         p.addLast(config.handler());
281 
282         return channel.executor().newSucceededFuture(channel);
283     }
284 
285     @Override
286     Channel newChannel(EventLoop eventLoop) throws Exception {
287         return channelFactory.newChannel(eventLoop);
288     }
289 
290     @Override
291     public Bootstrap validate() {
292         super.validate();
293         if (config.handler() == null) {
294             throw new IllegalStateException("handler not set");
295         }
296         if (config.channelFactory() == null) {
297             throw new IllegalStateException("channelFactory not set");
298         }
299         return this;
300     }
301 
302     @Override
303     @SuppressWarnings("CloneDoesntCallSuperClone")
304     public Bootstrap clone() {
305         return new Bootstrap(this);
306     }
307 
308     /**
309      * Returns a deep clone of this bootstrap which has the identical configuration except that it uses
310      * the given {@link EventLoopGroup}. This method is useful when making multiple {@link Channel}s with similar
311      * settings.
312      */
313     public Bootstrap clone(EventLoopGroup group) {
314         Bootstrap bs = new Bootstrap(this);
315         bs.group = group;
316         return bs;
317     }
318 
319     @Override
320     public final BootstrapConfig config() {
321         return config;
322     }
323 
324     final SocketAddress remoteAddress() {
325         return remoteAddress;
326     }
327 
328     final AddressResolverGroup<?> resolver() {
329         return resolver;
330     }
331 }