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  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.List;
36  
37  import org.apache.http.HttpHost;
38  import org.apache.http.annotation.Contract;
39  import org.apache.http.annotation.ThreadingBehavior;
40  import org.apache.http.util.Args;
41  import org.apache.http.util.LangUtils;
42  
43  /**
44   * The route for a request.
45   *
46   * @since 4.0
47   */
48  @Contract(threading = ThreadingBehavior.IMMUTABLE)
49  public final class HttpRoute implements RouteInfo, Cloneable {
50  
51      /** The target host to connect to. */
52      private final HttpHost targetHost;
53  
54      /**
55       * The local address to connect from.
56       * {@code null} indicates that the default should be used.
57       */
58      private final InetAddress localAddress;
59  
60      /** The proxy servers, if any. Never null. */
61      private final List<HttpHost> proxyChain;
62  
63      /** Whether the the route is tunnelled through the proxy. */
64      private final TunnelType tunnelled;
65  
66      /** Whether the route is layered. */
67      private final LayerType layered;
68  
69      /** Whether the route is (supposed to be) secure. */
70      private final boolean secure;
71  
72      private HttpRoute(final HttpHost target, final InetAddress local, final List<HttpHost> proxies,
73                       final boolean secure, final TunnelType tunnelled, final LayerType layered) {
74          Args.notNull(target, "Target host");
75          this.targetHost = normalize(target);
76          this.localAddress = local;
77          if (proxies != null && !proxies.isEmpty()) {
78              this.proxyChain = new ArrayList<HttpHost>(proxies);
79          } else {
80              this.proxyChain = null;
81          }
82          if (tunnelled == TunnelType.TUNNELLED) {
83              Args.check(this.proxyChain != null, "Proxy required if tunnelled");
84          }
85          this.secure       = secure;
86          this.tunnelled    = tunnelled != null ? tunnelled : TunnelType.PLAIN;
87          this.layered      = layered != null ? layered : LayerType.PLAIN;
88      }
89  
90      //TODO: to be removed in 5.0
91      private static int getDefaultPort(final String schemeName) {
92          if ("http".equalsIgnoreCase(schemeName)) {
93              return 80;
94          } else if ("https".equalsIgnoreCase(schemeName)) {
95              return 443;
96          } else {
97              return -1;
98          }
99  
100     }
101 
102     //TODO: to be removed in 5.0
103     private static HttpHost normalize(final HttpHost target) {
104         if (target.getPort() >= 0 ) {
105             return target;
106         } else {
107             final InetAddress address = target.getAddress();
108             final String schemeName = target.getSchemeName();
109             if (address != null) {
110                 return new HttpHost(address, getDefaultPort(schemeName), schemeName);
111             } else {
112                 final String hostName = target.getHostName();
113                 return new HttpHost(hostName, getDefaultPort(schemeName), schemeName);
114             }
115         }
116     }
117 
118     /**
119      * Creates a new route with all attributes specified explicitly.
120      *
121      * @param target    the host to which to route
122      * @param local     the local address to route from, or
123      *                  {@code null} for the default
124      * @param proxies   the proxy chain to use, or
125      *                  {@code null} for a direct route
126      * @param secure    {@code true} if the route is (to be) secure,
127      *                  {@code false} otherwise
128      * @param tunnelled the tunnel type of this route
129      * @param layered   the layering type of this route
130      */
131     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies,
132                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
133         this(target, local, proxies != null ? Arrays.asList(proxies) : null,
134                 secure, tunnelled, layered);
135     }
136 
137     /**
138      * Creates a new route with at most one proxy.
139      *
140      * @param target    the host to which to route
141      * @param local     the local address to route from, or
142      *                  {@code null} for the default
143      * @param proxy     the proxy to use, or
144      *                  {@code null} for a direct route
145      * @param secure    {@code true} if the route is (to be) secure,
146      *                  {@code false} otherwise
147      * @param tunnelled {@code true} if the route is (to be) tunnelled
148      *                  via the proxy,
149      *                  {@code false} otherwise
150      * @param layered   {@code true} if the route includes a
151      *                  layered protocol,
152      *                  {@code false} otherwise
153      */
154     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
155                      final boolean secure, final TunnelType tunnelled, final LayerType layered) {
156         this(target, local, proxy != null ? Collections.singletonList(proxy) : null,
157                 secure, tunnelled, layered);
158     }
159 
160     /**
161      * Creates a new direct route.
162      * That is a route without a proxy.
163      *
164      * @param target    the host to which to route
165      * @param local     the local address to route from, or
166      *                  {@code null} for the default
167      * @param secure    {@code true} if the route is (to be) secure,
168      *                  {@code false} otherwise
169      */
170     public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) {
171         this(target, local, Collections.<HttpHost>emptyList(), secure,
172                 TunnelType.PLAIN, LayerType.PLAIN);
173     }
174 
175     /**
176      * Creates a new direct insecure route.
177      *
178      * @param target    the host to which to route
179      */
180     public HttpRoute(final HttpHost target) {
181         this(target, null, Collections.<HttpHost>emptyList(), false,
182                 TunnelType.PLAIN, LayerType.PLAIN);
183     }
184 
185     /**
186      * Creates a new route through a proxy.
187      * When using this constructor, the {@code proxy} MUST be given.
188      * For convenience, it is assumed that a secure connection will be
189      * layered over a tunnel through the proxy.
190      *
191      * @param target    the host to which to route
192      * @param local     the local address to route from, or
193      *                  {@code null} for the default
194      * @param proxy     the proxy to use
195      * @param secure    {@code true} if the route is (to be) secure,
196      *                  {@code false} otherwise
197      */
198     public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy,
199                      final boolean secure) {
200         this(target, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure,
201              secure ? TunnelType.TUNNELLED : TunnelType.PLAIN,
202              secure ? LayerType.LAYERED    : LayerType.PLAIN);
203     }
204 
205     /**
206      * Creates a new plain route through a proxy.
207      *
208      * @param target    the host to which to route
209      * @param proxy     the proxy to use
210      *
211      * @since 4.3
212      */
213     public HttpRoute(final HttpHost target, final HttpHost proxy) {
214         this(target, null, proxy, false);
215     }
216 
217     @Override
218     public final HttpHost getTargetHost() {
219         return this.targetHost;
220     }
221 
222     @Override
223     public final InetAddress getLocalAddress() {
224         return this.localAddress;
225     }
226 
227     public final InetSocketAddress getLocalSocketAddress() {
228         return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
229     }
230 
231     @Override
232     public final int getHopCount() {
233         return proxyChain != null ? proxyChain.size() + 1 : 1;
234     }
235 
236     @Override
237     public final HttpHost getHopTarget(final int hop) {
238         Args.notNegative(hop, "Hop index");
239         final int hopcount = getHopCount();
240         Args.check(hop < hopcount, "Hop index exceeds tracked route length");
241         if (hop < hopcount - 1) {
242             return this.proxyChain.get(hop);
243         } else {
244             return this.targetHost;
245         }
246     }
247 
248     @Override
249     public final HttpHost getProxyHost() {
250         return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null;
251     }
252 
253     @Override
254     public final TunnelType getTunnelType() {
255         return this.tunnelled;
256     }
257 
258     @Override
259     public final boolean isTunnelled() {
260         return (this.tunnelled == TunnelType.TUNNELLED);
261     }
262 
263     @Override
264     public final LayerType getLayerType() {
265         return this.layered;
266     }
267 
268     @Override
269     public final boolean isLayered() {
270         return (this.layered == LayerType.LAYERED);
271     }
272 
273     @Override
274     public final boolean isSecure() {
275         return this.secure;
276     }
277 
278     /**
279      * Compares this route to another.
280      *
281      * @param obj         the object to compare with
282      *
283      * @return  {@code true} if the argument is the same route,
284      *          {@code false}
285      */
286     @Override
287     public final boolean equals(final Object obj) {
288         if (this == obj) {
289             return true;
290         }
291         if (obj instanceof HttpRoute) {
292             final HttpRoute that = (HttpRoute) obj;
293             return
294                 // Do the cheapest tests first
295                 (this.secure    == that.secure) &&
296                 (this.tunnelled == that.tunnelled) &&
297                 (this.layered   == that.layered) &&
298                 LangUtils.equals(this.targetHost, that.targetHost) &&
299                 LangUtils.equals(this.localAddress, that.localAddress) &&
300                 LangUtils.equals(this.proxyChain, that.proxyChain);
301         } else {
302             return false;
303         }
304     }
305 
306 
307     /**
308      * Generates a hash code for this route.
309      *
310      * @return  the hash code
311      */
312     @Override
313     public final int hashCode() {
314         int hash = LangUtils.HASH_SEED;
315         hash = LangUtils.hashCode(hash, this.targetHost);
316         hash = LangUtils.hashCode(hash, this.localAddress);
317         if (this.proxyChain != null) {
318             for (final HttpHost element : this.proxyChain) {
319                 hash = LangUtils.hashCode(hash, element);
320             }
321         }
322         hash = LangUtils.hashCode(hash, this.secure);
323         hash = LangUtils.hashCode(hash, this.tunnelled);
324         hash = LangUtils.hashCode(hash, this.layered);
325         return hash;
326     }
327 
328     /**
329      * Obtains a description of this route.
330      *
331      * @return  a human-readable representation of this route
332      */
333     @Override
334     public final String toString() {
335         final StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
336         if (this.localAddress != null) {
337             cab.append(this.localAddress);
338             cab.append("->");
339         }
340         cab.append('{');
341         if (this.tunnelled == TunnelType.TUNNELLED) {
342             cab.append('t');
343         }
344         if (this.layered == LayerType.LAYERED) {
345             cab.append('l');
346         }
347         if (this.secure) {
348             cab.append('s');
349         }
350         cab.append("}->");
351         if (this.proxyChain != null) {
352             for (final HttpHost aProxyChain : this.proxyChain) {
353                 cab.append(aProxyChain);
354                 cab.append("->");
355             }
356         }
357         cab.append(this.targetHost);
358         return cab.toString();
359     }
360 
361     // default implementation of clone() is sufficient
362     @Override
363     public Object clone() throws CloneNotSupportedException {
364         return super.clone();
365     }
366 
367 }