1 /* 2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java $ 3 * $Revision: 658785 $ 4 * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 * 25 * This software consists of voluntary contributions made by many 26 * individuals on behalf of the Apache Software Foundation. For more 27 * information on the Apache Software Foundation, please see 28 * <http://www.apache.org/>. 29 * 30 */ 31 32 package org.apache.http.impl.conn; 33 34 35 import java.net.InetAddress; 36 import java.net.InetSocketAddress; 37 import java.net.Proxy; 38 import java.net.ProxySelector; 39 import java.net.URI; 40 import java.net.URISyntaxException; 41 import java.util.List; 42 43 import org.apache.http.HttpException; 44 import org.apache.http.HttpHost; 45 import org.apache.http.HttpRequest; 46 import org.apache.http.protocol.HttpContext; 47 48 import org.apache.http.conn.routing.HttpRoute; 49 import org.apache.http.conn.routing.HttpRoutePlanner; 50 import org.apache.http.conn.scheme.Scheme; 51 import org.apache.http.conn.scheme.SchemeRegistry; 52 53 import org.apache.http.conn.params.ConnRouteParams; 54 import org.apache.http.conn.params.ConnRoutePNames; 55 56 57 /** 58 * Default implementation of an {@link HttpRoutePlanner}. 59 * This implementation is based on {@link java.net.ProxySelector}. 60 * By default, it will pick up the proxy settings of the JVM, either 61 * from system properties or from the browser running the application. 62 * Additionally, it interprets some 63 * {@link org.apache.http.conn.params.ConnRoutePNames parameters}, 64 * though not the {@link 65 * org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. 66 */ 67 public class ProxySelectorRoutePlanner implements HttpRoutePlanner { 68 69 /** The scheme registry. */ 70 protected SchemeRegistry schemeRegistry; 71 72 /** The proxy selector to use, or <code>null</code> for system default. */ 73 protected ProxySelector proxySelector; 74 75 76 /** 77 * Creates a new proxy selector route planner. 78 * 79 * @param schreg the scheme registry 80 * @param prosel the proxy selector, or 81 * <code>null</code> for the system default 82 */ ProxySelectorRoutePlanner(SchemeRegistry schreg, ProxySelector prosel)83 public ProxySelectorRoutePlanner(SchemeRegistry schreg, 84 ProxySelector prosel) { 85 86 if (schreg == null) { 87 throw new IllegalArgumentException 88 ("SchemeRegistry must not be null."); 89 } 90 schemeRegistry = schreg; 91 proxySelector = prosel; 92 } 93 94 95 /** 96 * Obtains the proxy selector to use. 97 * 98 * @return the proxy selector, or <code>null</code> for the system default 99 */ getProxySelector()100 public ProxySelector getProxySelector() { 101 return this.proxySelector; 102 } 103 104 105 /** 106 * Sets the proxy selector to use. 107 * 108 * @param prosel the proxy selector, or 109 * <code>null</code> to use the system default 110 */ setProxySelector(ProxySelector prosel)111 public void setProxySelector(ProxySelector prosel) { 112 this.proxySelector = prosel; 113 } 114 115 116 117 // non-javadoc, see interface HttpRoutePlanner determineRoute(HttpHost target, HttpRequest request, HttpContext context)118 public HttpRoute determineRoute(HttpHost target, 119 HttpRequest request, 120 HttpContext context) 121 throws HttpException { 122 123 if (request == null) { 124 throw new IllegalStateException 125 ("Request must not be null."); 126 } 127 128 // If we have a forced route, we can do without a target. 129 HttpRoute route = 130 ConnRouteParams.getForcedRoute(request.getParams()); 131 if (route != null) 132 return route; 133 134 // If we get here, there is no forced route. 135 // So we need a target to compute a route. 136 137 if (target == null) { 138 throw new IllegalStateException 139 ("Target host must not be null."); 140 } 141 142 final InetAddress local = 143 ConnRouteParams.getLocalAddress(request.getParams()); 144 145 // BEGIN android-changed 146 // If the client or request explicitly specifies a proxy (or no 147 // proxy), prefer that over the ProxySelector's VM-wide default. 148 HttpHost proxy = (HttpHost) request.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY); 149 if (proxy == null) { 150 proxy = determineProxy(target, request, context); 151 } else if (ConnRouteParams.NO_HOST.equals(proxy)) { 152 // value is explicitly unset 153 proxy = null; 154 } 155 // END android-changed 156 157 final Scheme schm = 158 this.schemeRegistry.getScheme(target.getSchemeName()); 159 // as it is typically used for TLS/SSL, we assume that 160 // a layered scheme implies a secure connection 161 final boolean secure = schm.isLayered(); 162 163 if (proxy == null) { 164 route = new HttpRoute(target, local, secure); 165 } else { 166 route = new HttpRoute(target, local, proxy, secure); 167 } 168 return route; 169 } 170 171 172 /** 173 * Determines a proxy for the given target. 174 * 175 * @param target the planned target, never <code>null</code> 176 * @param request the request to be sent, never <code>null</code> 177 * @param context the context, or <code>null</code> 178 * 179 * @return the proxy to use, or <code>null</code> for a direct route 180 * 181 * @throws HttpException 182 * in case of system proxy settings that cannot be handled 183 */ determineProxy(HttpHost target, HttpRequest request, HttpContext context)184 protected HttpHost determineProxy(HttpHost target, 185 HttpRequest request, 186 HttpContext context) 187 throws HttpException { 188 189 // the proxy selector can be 'unset', so we better deal with null here 190 ProxySelector psel = this.proxySelector; 191 if (psel == null) 192 psel = ProxySelector.getDefault(); 193 if (psel == null) 194 return null; 195 196 URI targetURI = null; 197 try { 198 targetURI = new URI(target.toURI()); 199 } catch (URISyntaxException usx) { 200 throw new HttpException 201 ("Cannot convert host to URI: " + target, usx); 202 } 203 List<Proxy> proxies = psel.select(targetURI); 204 205 Proxy p = chooseProxy(proxies, target, request, context); 206 207 HttpHost result = null; 208 if (p.type() == Proxy.Type.HTTP) { 209 // convert the socket address to an HttpHost 210 if (!(p.address() instanceof InetSocketAddress)) { 211 throw new HttpException 212 ("Unable to handle non-Inet proxy address: "+p.address()); 213 } 214 final InetSocketAddress isa = (InetSocketAddress) p.address(); 215 // assume default scheme (http) 216 result = new HttpHost(getHost(isa), isa.getPort()); 217 } 218 219 return result; 220 } 221 222 223 /** 224 * Obtains a host from an {@link InetSocketAddress}. 225 * 226 * @param isa the socket address 227 * 228 * @return a host string, either as a symbolic name or 229 * as a literal IP address string 230 * <br/> 231 * (TODO: determine format for IPv6 addresses, with or without [brackets]) 232 */ getHost(InetSocketAddress isa)233 protected String getHost(InetSocketAddress isa) { 234 235 //@@@ Will this work with literal IPv6 addresses, or do we 236 //@@@ need to wrap these in [] for the string representation? 237 //@@@ Having it in this method at least allows for easy workarounds. 238 return isa.isUnresolved() ? 239 isa.getHostName() : isa.getAddress().getHostAddress(); 240 241 } 242 243 244 /* 245 * Chooses a proxy from a list of available proxies. 246 * The default implementation just picks the first non-SOCKS proxy 247 * from the list. If there are only SOCKS proxies, 248 * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. 249 * Derived classes may implement more advanced strategies, 250 * such as proxy rotation if there are multiple options. 251 * 252 * @param proxies the list of proxies to choose from, 253 * never <code>null</code> or empty 254 * @param target the planned target, never <code>null</code> 255 * @param request the request to be sent, never <code>null</code> 256 * @param context the context, or <code>null</code> 257 * 258 * @return a proxy of type {@link Proxy.Type#DIRECT DIRECT} 259 * or {@link Proxy.Type#HTTP HTTP}, never <code>null</code> 260 */ chooseProxy(List<Proxy> proxies, HttpHost target, HttpRequest request, HttpContext context)261 protected Proxy chooseProxy(List<Proxy> proxies, 262 HttpHost target, 263 HttpRequest request, 264 HttpContext context) { 265 266 if ((proxies == null) || proxies.isEmpty()) { 267 throw new IllegalArgumentException 268 ("Proxy list must not be empty."); 269 } 270 271 Proxy result = null; 272 273 // check the list for one we can use 274 for (int i=0; (result == null) && (i < proxies.size()); i++) { 275 276 Proxy p = proxies.get(i); 277 switch (p.type()) { 278 279 case DIRECT: 280 case HTTP: 281 result = p; 282 break; 283 284 case SOCKS: 285 // SOCKS hosts are not handled on the route level. 286 // The socket may make use of the SOCKS host though. 287 break; 288 } 289 } 290 291 if (result == null) { 292 //@@@ log as warning or info that only a socks proxy is available? 293 // result can only be null if all proxies are socks proxies 294 // socks proxies are not handled on the route planning level 295 result = Proxy.NO_PROXY; 296 } 297 298 return result; 299 } 300 301 } // class ProxySelectorRoutePlanner 302 303