View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http;
29  
30  import java.net.InetAddress;
31  import java.net.InetSocketAddress;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.List;
36  import java.util.Objects;
37  
38  import org.apache.hc.core5.annotation.Contract;
39  import org.apache.hc.core5.annotation.ThreadingBehavior;
40  import org.apache.hc.core5.http.HttpHost;
41  import org.apache.hc.core5.net.NamedEndpoint;
42  import org.apache.hc.core5.util.Args;
43  import org.apache.hc.core5.util.LangUtils;
44  
45  /**
46   * Connection route definition for HTTP requests.
47   *
48   * @since 4.0
49   */
50  @Contract(threading = ThreadingBehavior.IMMUTABLE)
51  public final class HttpRoute implements RouteInfo, Cloneable {
52  
53      /** The target host to connect to. */
54      private final HttpHost targetHost;
55  
56      /** The target name, if different from the target host, {@code null} otherwise. */
57      private final NamedEndpoint targetName;
58  
59      /**
60       * The local address to connect from.
61       * {@code null} indicates that the default should be used.
62       */
63      private final InetAddress localAddress;
64  
65      /** The proxy servers, if any. Never null. */
66      private final List<HttpHost> proxyChain;
67  
68      /** Whether the the route is tunnelled through the proxy. */
69      private final TunnelType tunnelled;
70  
71      /** Whether the route is layered. */
72      private final LayerType layered;
73  
74      /** Whether the route is (supposed to be) secure. */
75      private final boolean secure;
76  
77      HttpRoute(final HttpHost targetHost,
78                final NamedEndpoint targetName,
79                final InetAddress local,
80                final List<HttpHost> proxies,
81                final boolean secure,
82                final TunnelType tunnelled,
83                final LayerType layered) {
84          Args.notNull(targetHost, "Target host");
85          Args.notNegative(targetHost.getPort(), "Target port");
86          this.targetName = targetName;
87          this.targetHost = targetHost;
88          this.localAddress = local;
89          if (proxies != null && !proxies.isEmpty()) {
90              this.proxyChain = new ArrayList<>(proxies);
91          } else {
92              this.proxyChain = null;
93          }
94          if (tunnelled == TunnelType.TUNNELLED) {
95              Args.check(this.proxyChain != null, "Proxy required if tunnelled");
96          }
97          this.secure       = secure;
98          this.tunnelled    = tunnelled != null ? tunnelled : TunnelType.PLAIN;
99          this.layered      = layered != null ? layered : LayerType.PLAIN;
100     }
101 
102     /**
103      * Creates a new route with all attributes specified explicitly.
104      *
105      * @param target    the host to which to route
106      * @param local     the local address to route from, or
107      *                  {@code null} for the default
108      * @param proxies   the proxy chain to use, or
109      *                  {@code null} for a direct route
110      * @param secure    {@code true} if the route is (to be) secure,
111      *                  {@code false} otherwise
112      * @param tunnelled the tunnel type of this route
113      * @param layered   the layering type of this route
114      */
115     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
116                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
117         this(target, null, local, proxies != null ? Arrays.asList(proxies) : null,
118                 secure, tunnelled, layered);
119     }
120 
121     /**
122      * Creates a new route with all attributes specified explicitly.
123      *
124      * @param target    the host to which to route
125      * @param targetName the target targetName if differs from the target host.
126      * @param local     the local address to route from, or
127      *                  {@code null} for the default
128      * @param proxies   the proxy chain to use, or
129      *                  {@code null} for a direct route
130      * @param secure    {@code true} if the route is (to be) secure,
131      *                  {@code false} otherwise
132      * @param tunnelled the tunnel type of this route
133      * @param layered   the layering type of this route
134      *
135      * @since 5.4
136      */
137     public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local, final HttpHost[] proxies,
138                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
139         this(target, targetName, local, proxies != null ? Arrays.asList(proxies) : null,
140                 secure, tunnelled, layered);
141     }
142 
143     /**
144      * Creates a new route with at most one proxy.
145      *
146      * @param target    the host to which to route
147      * @param local     the local address to route from, or
148      *                  {@code null} for the default
149      * @param proxy     the proxy to use, or
150      *                  {@code null} for a direct route
151      * @param secure    {@code true} if the route is (to be) secure,
152      *                  {@code false} otherwise
153      * @param tunnelled {@code true} if the route is (to be) tunnelled
154      *                  via the proxy,
155      *                  {@code false} otherwise
156      * @param layered   {@code true} if the route includes a
157      *                  layered protocol,
158      *                  {@code false} otherwise
159      */
160     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
161                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
162         this(target, null, local, proxy != null ? Collections.singletonList(proxy) : null,
163                 secure, tunnelled, layered);
164     }
165 
166     /**
167      * Creates a new direct route.
168      * That is a route without a proxy.
169      *
170      * @param target    the host to which to route
171      * @param local     the local address to route from, or
172      *                  {@code null} for the default
173      * @param secure    {@code true} if the route is (to be) secure,
174      *                  {@code false} otherwise
175      */
176     public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
177         this(target, null, local, Collections.emptyList(), secure, TunnelType.PLAIN, LayerType.PLAIN);
178     }
179 
180     /**
181      * Creates a new direct route. That is a route without a proxy.
182      *
183      * @param target    the host to which to route
184      * @param targetName the target targetName if differs from the target host.
185      * @param local     the local address to route from, or
186      *                  {@code null} for the default
187      * @param secure    {@code true} if the route is (to be) secure,
188      *                  {@code false} otherwise
189      *
190      * @since 5.4
191      */
192     public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local, final boolean secure) {
193         this(target, targetName, local, Collections.emptyList(), secure, TunnelType.PLAIN, LayerType.PLAIN);
194     }
195 
196     /**
197      * Creates a new direct insecure route.
198      *
199      * @param target    the host to which to route
200      */
201     public HttpRoute(final HttpHost target) {
202         this(target, null, null, Collections.emptyList(), false, TunnelType.PLAIN, LayerType.PLAIN);
203     }
204 
205     /**
206      * Creates a new route through a proxy.
207      * When using this constructor, the {@code proxy} MUST be given.
208      * For convenience, it is assumed that a secure connection will be
209      * layered over a tunnel through the proxy.
210      *
211      * @param target    the host to which to route
212      * @param targetName the target targetName if differs from the target host.
213      * @param local     the local address to route from, or
214      *                  {@code null} for the default
215      * @param proxy     the proxy to use
216      * @param secure    {@code true} if the route is (to be) secure,
217      *                  {@code false} otherwise
218      *
219      * @since 5.4
220      */
221     public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final InetAddress local,
222                      final HttpHost proxy, final boolean secure) {
223         this(target, targetName, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
224              secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
225              secure ? LayerType.LAYERED    : LayerType.PLAIN);
226     }
227 
228     /**
229      * Creates a new route through a proxy.
230      * When using this constructor, the {@code proxy} MUST be given.
231      * For convenience, it is assumed that a secure connection will be
232      * layered over a tunnel through the proxy.
233      *
234      * @param target    the host to which to route
235      * @param local     the local address to route from, or
236      *                  {@code null} for the default
237      * @param proxy     the proxy to use
238      * @param secure    {@code true} if the route is (to be) secure,
239      *                  {@code false} otherwise
240      */
241     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
242                      final boolean secure) {
243         this(target, null, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
244                 secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
245                 secure ? LayerType.LAYERED    : LayerType.PLAIN);
246     }
247 
248     /**
249      * Creates a new plain route through a proxy.
250      *
251      * @param target    the host to which to route
252      * @param proxy     the proxy to use
253      *
254      * @since 4.3
255      */
256     public HttpRoute(final HttpHost target, final HttpHost proxy) {
257         this(target, null, proxy, false);
258     }
259 
260     @Override
261     public HttpHost getTargetHost() {
262         return this.targetHost;
263     }
264 
265     /**
266      * @since 5.4
267      */
268     @Override
269     public NamedEndpoint getTargetName() {
270         return targetName;
271     }
272 
273     @Override
274     public InetAddress getLocalAddress() {
275         return this.localAddress;
276     }
277 
278     public InetSocketAddress getLocalSocketAddress() {
279         return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
280     }
281 
282     @Override
283     public int getHopCount() {
284         return proxyChain != null ? proxyChain.size() + 1 : 1;
285     }
286 
287     @Override
288     public HttpHost getHopTarget(final int hop) {
289         Args.notNegative(hop, "Hop index");
290         final int hopcount = getHopCount();
291         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
292         if (hop < hopcount - 1) {
293             return this.proxyChain.get(hop);
294         }
295         return this.targetHost;
296     }
297 
298     @Override
299     public HttpHost getProxyHost() {
300         return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null;
301     }
302 
303     @Override
304     public TunnelType getTunnelType() {
305         return this.tunnelled;
306     }
307 
308     @Override
309     public boolean isTunnelled() {
310         return (this.tunnelled == TunnelType.TUNNELLED);
311     }
312 
313     @Override
314     public LayerType getLayerType() {
315         return this.layered;
316     }
317 
318     @Override
319     public boolean isLayered() {
320         return (this.layered == LayerType.LAYERED);
321     }
322 
323     @Override
324     public boolean isSecure() {
325         return this.secure;
326     }
327 
328     /**
329      * Compares this route to another.
330      *
331      * @param obj         the object to compare with
332      *
333      * @return  {@code true} if the argument is the same route,
334      *          {@code false}
335      */
336     @Override
337     public boolean equals(final Object obj) {
338         if (this == obj) {
339             return true;
340         }
341         if (obj instanceof HttpRoute) {
342             final HttpRoute that = (HttpRoute) obj;
343             return
344                 // Do the cheapest tests first
345                 (this.secure    == that.secure) &&
346                 (this.tunnelled == that.tunnelled) &&
347                 (this.layered   == that.layered) &&
348                 Objects.equals(this.targetHost, that.targetHost) &&
349                 Objects.equals(this.targetName, that.targetName) &&
350                 Objects.equals(this.localAddress, that.localAddress) &&
351                 Objects.equals(this.proxyChain, that.proxyChain);
352         }
353         return false;
354     }
355 
356 
357     /**
358      * Generates a hash code for this route.
359      *
360      * @return  the hash code
361      */
362     @Override
363     public int hashCode() {
364         int hash = LangUtils.HASH_SEED;
365         hash = LangUtils.hashCode(hash, this.targetHost);
366         hash = LangUtils.hashCode(hash, this.targetName);
367         hash = LangUtils.hashCode(hash, this.localAddress);
368         if (this.proxyChain != null) {
369             for (final HttpHost element : this.proxyChain) {
370                 hash = LangUtils.hashCode(hash, element);
371             }
372         }
373         hash = LangUtils.hashCode(hash, this.secure);
374         hash = LangUtils.hashCode(hash, this.tunnelled);
375         hash = LangUtils.hashCode(hash, this.layered);
376         return hash;
377     }
378 
379     /**
380      * Obtains a description of this route.
381      *
382      * @return  a human-readable representation of this route
383      */
384     @Override
385     public String toString() {
386         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
387         if (this.localAddress != null) {
388             cab.append(this.localAddress);
389             cab.append("->");
390         }
391         cab.append('{');
392         if (this.tunnelled == TunnelType.TUNNELLED) {
393             cab.append('t');
394         }
395         if (this.layered == LayerType.LAYERED) {
396             cab.append('l');
397         }
398         if (this.secure) {
399             cab.append('s');
400         }
401         cab.append("}->");
402         if (this.proxyChain != null) {
403             for (final HttpHost aProxyChain : this.proxyChain) {
404                 cab.append(aProxyChain);
405                 cab.append("->");
406             }
407         }
408         if (this.targetName != null) {
409             cab.append(this.targetName);
410             cab.append("/");
411         }
412         cab.append("[");
413         cab.append(this.targetHost);
414         cab.append("]");
415         return cab.toString();
416     }
417 
418     // default implementation of clone() is sufficient
419     @Override
420     public Object clone() throws CloneNotSupportedException {
421         return super.clone();
422     }
423 
424 }