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 }