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