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