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