001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.conn.routing; 029 030import java.net.InetAddress; 031import java.net.InetSocketAddress; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Collections; 035import java.util.List; 036 037import org.apache.http.HttpHost; 038import org.apache.http.annotation.Contract; 039import org.apache.http.annotation.ThreadingBehavior; 040import org.apache.http.util.Args; 041import org.apache.http.util.LangUtils; 042 043/** 044 * The route for a request. 045 * 046 * @since 4.0 047 */ 048@Contract(threading = ThreadingBehavior.IMMUTABLE) 049public final class HttpRoute implements RouteInfo, Cloneable { 050 051 /** The target host to connect to. */ 052 private final HttpHost targetHost; 053 054 /** 055 * The local address to connect from. 056 * {@code null} indicates that the default should be used. 057 */ 058 private final InetAddress localAddress; 059 060 /** The proxy servers, if any. Never null. */ 061 private final List<HttpHost> proxyChain; 062 063 /** Whether the the route is tunnelled through the proxy. */ 064 private final TunnelType tunnelled; 065 066 /** Whether the route is layered. */ 067 private final LayerType layered; 068 069 /** Whether the route is (supposed to be) secure. */ 070 private final boolean secure; 071 072 private HttpRoute(final HttpHost target, final InetAddress local, final List<HttpHost> proxies, 073 final boolean secure, final TunnelType tunnelled, final LayerType layered) { 074 Args.notNull(target, "Target host"); 075 this.targetHost = normalize(target); 076 this.localAddress = local; 077 if (proxies != null && !proxies.isEmpty()) { 078 this.proxyChain = new ArrayList<HttpHost>(proxies); 079 } else { 080 this.proxyChain = null; 081 } 082 if (tunnelled == TunnelType.TUNNELLED) { 083 Args.check(this.proxyChain != null, "Proxy required if tunnelled"); 084 } 085 this.secure = secure; 086 this.tunnelled = tunnelled != null ? tunnelled : TunnelType.PLAIN; 087 this.layered = layered != null ? layered : LayerType.PLAIN; 088 } 089 090 //TODO: to be removed in 5.0 091 private static int getDefaultPort(final String schemeName) { 092 if ("http".equalsIgnoreCase(schemeName)) { 093 return 80; 094 } else if ("https".equalsIgnoreCase(schemeName)) { 095 return 443; 096 } else { 097 return -1; 098 } 099 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 } else { 107 final InetAddress address = target.getAddress(); 108 final String schemeName = target.getSchemeName(); 109 if (address != null) { 110 return new HttpHost(address, getDefaultPort(schemeName), schemeName); 111 } else { 112 final String hostName = target.getHostName(); 113 return new HttpHost(hostName, getDefaultPort(schemeName), schemeName); 114 } 115 } 116 } 117 118 /** 119 * Creates a new route with all attributes specified explicitly. 120 * 121 * @param target the host to which to route 122 * @param local the local address to route from, or 123 * {@code null} for the default 124 * @param proxies the proxy chain to use, or 125 * {@code null} for a direct route 126 * @param secure {@code true} if the route is (to be) secure, 127 * {@code false} otherwise 128 * @param tunnelled the tunnel type of this route 129 * @param layered the layering type of this route 130 */ 131 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost[] proxies, 132 final boolean secure, final TunnelType tunnelled, final LayerType layered) { 133 this(target, local, proxies != null ? Arrays.asList(proxies) : null, 134 secure, tunnelled, layered); 135 } 136 137 /** 138 * Creates a new route with at most one proxy. 139 * 140 * @param target the host to which to route 141 * @param local the local address to route from, or 142 * {@code null} for the default 143 * @param proxy the proxy to use, or 144 * {@code null} for a direct route 145 * @param secure {@code true} if the route is (to be) secure, 146 * {@code false} otherwise 147 * @param tunnelled {@code true} if the route is (to be) tunnelled 148 * via the proxy, 149 * {@code false} otherwise 150 * @param layered {@code true} if the route includes a 151 * layered protocol, 152 * {@code false} otherwise 153 */ 154 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy, 155 final boolean secure, final TunnelType tunnelled, final LayerType layered) { 156 this(target, local, proxy != null ? Collections.singletonList(proxy) : null, 157 secure, tunnelled, layered); 158 } 159 160 /** 161 * Creates a new direct route. 162 * That is a route without a proxy. 163 * 164 * @param target the host to which to route 165 * @param local the local address to route from, or 166 * {@code null} for the default 167 * @param secure {@code true} if the route is (to be) secure, 168 * {@code false} otherwise 169 */ 170 public HttpRoute(final HttpHost target, final InetAddress local, final boolean secure) { 171 this(target, local, Collections.<HttpHost>emptyList(), secure, 172 TunnelType.PLAIN, LayerType.PLAIN); 173 } 174 175 /** 176 * Creates a new direct insecure route. 177 * 178 * @param target the host to which to route 179 */ 180 public HttpRoute(final HttpHost target) { 181 this(target, null, Collections.<HttpHost>emptyList(), false, 182 TunnelType.PLAIN, LayerType.PLAIN); 183 } 184 185 /** 186 * Creates a new route through a proxy. 187 * When using this constructor, the {@code proxy} MUST be given. 188 * For convenience, it is assumed that a secure connection will be 189 * layered over a tunnel through the proxy. 190 * 191 * @param target the host to which to route 192 * @param local the local address to route from, or 193 * {@code null} for the default 194 * @param proxy the proxy to use 195 * @param secure {@code true} if the route is (to be) secure, 196 * {@code false} otherwise 197 */ 198 public HttpRoute(final HttpHost target, final InetAddress local, final HttpHost proxy, 199 final boolean secure) { 200 this(target, local, Collections.singletonList(Args.notNull(proxy, "Proxy host")), secure, 201 secure ? TunnelType.TUNNELLED : TunnelType.PLAIN, 202 secure ? LayerType.LAYERED : LayerType.PLAIN); 203 } 204 205 /** 206 * Creates a new plain route through a proxy. 207 * 208 * @param target the host to which to route 209 * @param proxy the proxy to use 210 * 211 * @since 4.3 212 */ 213 public HttpRoute(final HttpHost target, final HttpHost proxy) { 214 this(target, null, proxy, false); 215 } 216 217 @Override 218 public final HttpHost getTargetHost() { 219 return this.targetHost; 220 } 221 222 @Override 223 public final InetAddress getLocalAddress() { 224 return this.localAddress; 225 } 226 227 public final InetSocketAddress getLocalSocketAddress() { 228 return this.localAddress != null ? new InetSocketAddress(this.localAddress, 0) : null; 229 } 230 231 @Override 232 public final int getHopCount() { 233 return proxyChain != null ? proxyChain.size() + 1 : 1; 234 } 235 236 @Override 237 public final HttpHost getHopTarget(final int hop) { 238 Args.notNegative(hop, "Hop index"); 239 final int hopcount = getHopCount(); 240 Args.check(hop < hopcount, "Hop index exceeds tracked route length"); 241 if (hop < hopcount - 1) { 242 return this.proxyChain.get(hop); 243 } else { 244 return this.targetHost; 245 } 246 } 247 248 @Override 249 public final HttpHost getProxyHost() { 250 return proxyChain != null && !this.proxyChain.isEmpty() ? this.proxyChain.get(0) : null; 251 } 252 253 @Override 254 public final TunnelType getTunnelType() { 255 return this.tunnelled; 256 } 257 258 @Override 259 public final boolean isTunnelled() { 260 return (this.tunnelled == TunnelType.TUNNELLED); 261 } 262 263 @Override 264 public final LayerType getLayerType() { 265 return this.layered; 266 } 267 268 @Override 269 public final boolean isLayered() { 270 return (this.layered == LayerType.LAYERED); 271 } 272 273 @Override 274 public final boolean isSecure() { 275 return this.secure; 276 } 277 278 /** 279 * Compares this route to another. 280 * 281 * @param obj the object to compare with 282 * 283 * @return {@code true} if the argument is the same route, 284 * {@code false} 285 */ 286 @Override 287 public final boolean equals(final Object obj) { 288 if (this == obj) { 289 return true; 290 } 291 if (obj instanceof HttpRoute) { 292 final HttpRoute that = (HttpRoute) obj; 293 return 294 // Do the cheapest tests first 295 (this.secure == that.secure) && 296 (this.tunnelled == that.tunnelled) && 297 (this.layered == that.layered) && 298 LangUtils.equals(this.targetHost, that.targetHost) && 299 LangUtils.equals(this.localAddress, that.localAddress) && 300 LangUtils.equals(this.proxyChain, that.proxyChain); 301 } else { 302 return false; 303 } 304 } 305 306 307 /** 308 * Generates a hash code for this route. 309 * 310 * @return the hash code 311 */ 312 @Override 313 public final int hashCode() { 314 int hash = LangUtils.HASH_SEED; 315 hash = LangUtils.hashCode(hash, this.targetHost); 316 hash = LangUtils.hashCode(hash, this.localAddress); 317 if (this.proxyChain != null) { 318 for (final HttpHost element : this.proxyChain) { 319 hash = LangUtils.hashCode(hash, element); 320 } 321 } 322 hash = LangUtils.hashCode(hash, this.secure); 323 hash = LangUtils.hashCode(hash, this.tunnelled); 324 hash = LangUtils.hashCode(hash, this.layered); 325 return hash; 326 } 327 328 /** 329 * Obtains a description of this route. 330 * 331 * @return a human-readable representation of this route 332 */ 333 @Override 334 public final String toString() { 335 final StringBuilder cab = new StringBuilder(50 + getHopCount()*30); 336 if (this.localAddress != null) { 337 cab.append(this.localAddress); 338 cab.append("->"); 339 } 340 cab.append('{'); 341 if (this.tunnelled == TunnelType.TUNNELLED) { 342 cab.append('t'); 343 } 344 if (this.layered == LayerType.LAYERED) { 345 cab.append('l'); 346 } 347 if (this.secure) { 348 cab.append('s'); 349 } 350 cab.append("}->"); 351 if (this.proxyChain != null) { 352 for (final HttpHost aProxyChain : this.proxyChain) { 353 cab.append(aProxyChain); 354 cab.append("->"); 355 } 356 } 357 cab.append(this.targetHost); 358 return cab.toString(); 359 } 360 361 // default implementation of clone() is sufficient 362 @Override 363 public Object clone() throws CloneNotSupportedException { 364 return super.clone(); 365 } 366 367}