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.http.conn.routing;
29  
30  import java.net.InetAddress;
31  import java.net.InetSocketAddress;
32  
33  import org.apache.http.HttpHost;
34  import org.apache.http.annotation.Immutable;
35  import org.apache.http.util.Args;
36  import org.apache.http.util.LangUtils;
37  
38  /**
39   * The route for a request.
40   * Instances of this class are unmodifiable and therefore suitable
41   * for use as lookup keys.
42   *
43   * @since 4.0
44   */
45  @Immutable
46  public final class HttpRoute implements RouteInfo, Cloneable {
47  
48      private static final HttpHost[] EMPTY_HTTP_HOST_ARRAY = new HttpHost[]{};
49  
50      /** The target host to connect to. */
51      private final HttpHost targetHost;
52  
53      /**
54       * The local address to connect from.
55       * <code>null</code> indicates that the default should be used.
56       */
57      private final InetAddress localAddress;
58  
59      /** The proxy servers, if any. Never null. */
60      private final HttpHost[] proxyChain;
61  
62      /** Whether the the route is tunnelled through the proxy. */
63      private final TunnelType tunnelled;
64  
65      /** Whether the route is layered. */
66      private final LayerType layered;
67  
68      /** Whether the route is (supposed to be) secure. */
69      private final boolean secure;
70  
71  
72      /**
73       * Internal, fully-specified constructor.
74       * This constructor does <i>not</i> clone the proxy chain array,
75       * nor test it for <code>null</code> elements. This conversion and
76       * check is the responsibility of the public constructors.
77       * The order of arguments here is different from the similar public
78       * constructor, as required by Java.
79       *
80       * @param local     the local address to route from, or
81       *                  <code>null</code> for the default
82       * @param target    the host to which to route
83       * @param proxies   the proxy chain to use, or
84       *                  <code>null</code> for a direct route
85       * @param secure    <code>true</code> if the route is (to be) secure,
86       *                  <code>false</code> otherwise
87       * @param tunnelled the tunnel type of this route, or
88       *                  <code>null</code> for PLAIN
89       * @param layered   the layering type of this route, or
90       *                  <code>null</code> for PLAIN
91       */
92      private HttpRoute(final InetAddress local,
93                        final HttpHost target, final HttpHost[] proxies,
94                        final boolean secure,
95                        TunnelType tunnelled, LayerType layered) {
96          Args.notNull(target, "Target host");
97          Args.notNull(proxies, "Array of proxy hosts");
98          for (final HttpHost proxy: proxies) {
99              Args.notNull(proxy, "Proxy host");
100         }
101         if (tunnelled == TunnelType.TUNNELLED) {
102             Args.check(proxies.length > 0, "Proxy required if tunnelled");
103         }
104         // tunnelled is already checked above, that is in line with the default
105         if (tunnelled == null) {
106             tunnelled = TunnelType.PLAIN;
107         }
108         if (layered == null) {
109             layered = LayerType.PLAIN;
110         }
111 
112         this.targetHost   = target;
113         this.localAddress = local;
114         this.proxyChain   = proxies;
115         this.secure       = secure;
116         this.tunnelled    = tunnelled;
117         this.layered      = layered;
118     }
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 local     the local address to route from, or
126      *                  <code>null</code> for the default
127      * @param proxies   the proxy chain to use, or
128      *                  <code>null</code> for a direct route
129      * @param secure    <code>true</code> if the route is (to be) secure,
130      *                  <code>false</code> otherwise
131      * @param tunnelled the tunnel type of this route
132      * @param layered   the layering type of this route
133      */
134     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
135                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
136         this(local, target, toChain(proxies), secure, tunnelled, layered);
137     }
138 
139 
140     /**
141      * Creates a new route with at most one proxy.
142      *
143      * @param target    the host to which to route
144      * @param local     the local address to route from, or
145      *                  <code>null</code> for the default
146      * @param proxy     the proxy to use, or
147      *                  <code>null</code> for a direct route
148      * @param secure    <code>true</code> if the route is (to be) secure,
149      *                  <code>false</code> otherwise
150      * @param tunnelled <code>true</code> if the route is (to be) tunnelled
151      *                  via the proxy,
152      *                  <code>false</code> otherwise
153      * @param layered   <code>true</code> if the route includes a
154      *                  layered protocol,
155      *                  <code>false</code> otherwise
156      */
157     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
158                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
159         this(local, target, toChain(proxy), secure, tunnelled, layered);
160     }
161 
162 
163     /**
164      * Creates a new direct route.
165      * That is a route without a proxy.
166      *
167      * @param target    the host to which to route
168      * @param local     the local address to route from, or
169      *                  <code>null</code> for the default
170      * @param secure    <code>true</code> if the route is (to be) secure,
171      *                  <code>false</code> otherwise
172      */
173     public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
174         this(local, target, EMPTY_HTTP_HOST_ARRAY, secure, TunnelType.PLAIN, LayerType.PLAIN);
175     }
176 
177 
178     /**
179      * Creates a new direct insecure route.
180      *
181      * @param target    the host to which to route
182      */
183     public HttpRoute(final HttpHost target) {
184         this(null, target, EMPTY_HTTP_HOST_ARRAY, false, TunnelType.PLAIN, LayerType.PLAIN);
185     }
186 
187 
188     /**
189      * Creates a new route through a proxy.
190      * When using this constructor, the <code>proxy</code> MUST be given.
191      * For convenience, it is assumed that a secure connection will be
192      * layered over a tunnel through the proxy.
193      *
194      * @param target    the host to which to route
195      * @param local     the local address to route from, or
196      *                  <code>null</code> for the default
197      * @param proxy     the proxy to use
198      * @param secure    <code>true</code> if the route is (to be) secure,
199      *                  <code>false</code> otherwise
200      */
201     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
202                      final boolean secure) {
203         this(local, target, toChain(proxy), secure,
204              secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
205              secure ? LayerType.LAYERED    : LayerType.PLAIN);
206         Args.notNull(proxy, "Proxy host");
207     }
208 
209 
210     /**
211      * Helper to convert a proxy to a proxy chain.
212      *
213      * @param proxy     the only proxy in the chain, or <code>null</code>
214      *
215      * @return  a proxy chain array, may be empty (never null)
216      */
217     private static HttpHost[] toChain(final HttpHost proxy) {
218         if (proxy == null) {
219             return EMPTY_HTTP_HOST_ARRAY;
220         }
221 
222         return new HttpHost[]{ proxy };
223     }
224 
225 
226     /**
227      * Helper to duplicate and check a proxy chain.
228      * <code>null</code> is converted to an empty proxy chain.
229      *
230      * @param proxies   the proxy chain to duplicate, or <code>null</code>
231      *
232      * @return  a new proxy chain array, may be empty (never null)
233      */
234     private static HttpHost[] toChain(final HttpHost[] proxies) {
235         if ((proxies == null) || (proxies.length < 1)) {
236             return EMPTY_HTTP_HOST_ARRAY;
237         }
238         // copy the proxy chain, the traditional way
239         final HttpHost[] result = new HttpHost[proxies.length];
240         System.arraycopy(proxies, 0, result, 0, proxies.length);
241 
242         return result;
243     }
244 
245 
246 
247     // non-JavaDoc, see interface RouteInfo
248     public final HttpHost getTargetHost() {
249         return this.targetHost;
250     }
251 
252 
253     // non-JavaDoc, see interface RouteInfo
254     public final InetAddress getLocalAddress() {
255         return this.localAddress;
256     }
257 
258 
259     public final InetSocketAddress getLocalSocketAddress() {
260         return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
261     }
262 
263     public final int getHopCount() {
264         return proxyChain.length+1;
265     }
266 
267 
268     public final HttpHost getHopTarget(final int hop) {
269         Args.notNegative(hop, "Hop index");
270         final int hopcount = getHopCount();
271         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
272 
273         HttpHost result = null;
274         if (hop < hopcount-1) {
275             result = this.proxyChain[hop];
276         } else {
277             result = this.targetHost;
278         }
279 
280         return result;
281     }
282 
283 
284     public final HttpHost getProxyHost() {
285         return (this.proxyChain.length == 0) ? null : this.proxyChain[0];
286     }
287 
288 
289     public final TunnelType getTunnelType() {
290         return this.tunnelled;
291     }
292 
293 
294     public final boolean isTunnelled() {
295         return (this.tunnelled == TunnelType.TUNNELLED);
296     }
297 
298 
299     public final LayerType getLayerType() {
300         return this.layered;
301     }
302 
303 
304     public final boolean isLayered() {
305         return (this.layered == LayerType.LAYERED);
306     }
307 
308 
309     public final boolean isSecure() {
310         return this.secure;
311     }
312 
313 
314     /**
315      * Compares this route to another.
316      *
317      * @param obj         the object to compare with
318      *
319      * @return  <code>true</code> if the argument is the same route,
320      *          <code>false</code>
321      */
322     @Override
323     public final boolean equals(final Object obj) {
324         if (this == obj) {
325             return true;
326         }
327         if (obj instanceof HttpRoute) {
328             final HttpRoute that = (HttpRoute) obj;
329             return
330                 // Do the cheapest tests first
331                 (this.secure    == that.secure) &&
332                 (this.tunnelled == that.tunnelled) &&
333                 (this.layered   == that.layered) &&
334                 LangUtils.equals(this.targetHost, that.targetHost) &&
335                 LangUtils.equals(this.localAddress, that.localAddress) &&
336                 LangUtils.equals(this.proxyChain, that.proxyChain);
337         } else {
338             return false;
339         }
340     }
341 
342 
343     /**
344      * Generates a hash code for this route.
345      *
346      * @return  the hash code
347      */
348     @Override
349     public final int hashCode() {
350         int hash = LangUtils.HASH_SEED;
351         hash = LangUtils.hashCode(hash, this.targetHost);
352         hash = LangUtils.hashCode(hash, this.localAddress);
353         for (final HttpHost element : this.proxyChain) {
354             hash = LangUtils.hashCode(hash, element);
355         }
356         hash = LangUtils.hashCode(hash, this.secure);
357         hash = LangUtils.hashCode(hash, this.tunnelled);
358         hash = LangUtils.hashCode(hash, this.layered);
359         return hash;
360     }
361 
362 
363     /**
364      * Obtains a description of this route.
365      *
366      * @return  a human-readable representation of this route
367      */
368     @Override
369     public final String toString() {
370         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
371         if (this.localAddress != null) {
372             cab.append(this.localAddress);
373             cab.append("->");
374         }
375         cab.append('{');
376         if (this.tunnelled == TunnelType.TUNNELLED) {
377             cab.append('t');
378         }
379         if (this.layered == LayerType.LAYERED) {
380             cab.append('l');
381         }
382         if (this.secure) {
383             cab.append('s');
384         }
385         cab.append("}->");
386         for (final HttpHost aProxyChain : this.proxyChain) {
387             cab.append(aProxyChain);
388             cab.append("->");
389         }
390         cab.append(this.targetHost);
391         return cab.toString();
392     }
393 
394 
395     // default implementation of clone() is sufficient
396     @Override
397     public Object clone() throws CloneNotSupportedException {
398         return super.clone();
399     }
400 
401 }