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 ch.boye.httpclientandroidlib.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 ch.boye.httpclientandroidlib.HttpHost;
38 import ch.boye.httpclientandroidlib.annotation.Immutable;
39 import ch.boye.httpclientandroidlib.util.Args;
40 import ch.boye.httpclientandroidlib.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 
HttpRoute(final HttpHost target, final InetAddress local, final List<HttpHost> proxies, final boolean secure, final TunnelType tunnelled, final LayerType layered)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      */
HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies, final boolean secure, final TunnelType tunnelled, final LayerType layered)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      */
HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy, final boolean secure, final TunnelType tunnelled, final LayerType layered)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      */
HttpRoute(final HttpHost target, final InetAddress local, final boolean secure)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      */
HttpRoute(final HttpHost target)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      */
HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy, final boolean secure)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      */
HttpRoute(final HttpHost target, final HttpHost proxy)184     public HttpRoute(final HttpHost target, final HttpHost proxy) {
185         this(target, null, proxy, false);
186     }
187 
getTargetHost()188     public final HttpHost getTargetHost() {
189         return this.targetHost;
190     }
191 
getLocalAddress()192     public final InetAddress getLocalAddress() {
193         return this.localAddress;
194     }
195 
getLocalSocketAddress()196     public final InetSocketAddress getLocalSocketAddress() {
197         return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null;
198     }
199 
getHopCount()200     public final int getHopCount() {
201         return proxyChain != null ? proxyChain.size() + 1 : 1;
202     }
203 
getHopTarget(final int hop)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 }
329