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