Home | History | Annotate | Download | only in conn
      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 
     55 
     56 /**
     57  * Default implementation of an {@link HttpRoutePlanner}.
     58  * This implementation is based on {@link java.net.ProxySelector}.
     59  * By default, it will pick up the proxy settings of the JVM, either
     60  * from system properties or from the browser running the application.
     61  * Additionally, it interprets some
     62  * {@link org.apache.http.conn.params.ConnRoutePNames parameters},
     63  * though not the {@link
     64  * org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}.
     65  */
     66 public class ProxySelectorRoutePlanner implements HttpRoutePlanner {
     67 
     68     /** The scheme registry. */
     69     protected SchemeRegistry schemeRegistry;
     70 
     71     /** The proxy selector to use, or <code>null</code> for system default. */
     72     protected ProxySelector proxySelector;
     73 
     74 
     75     /**
     76      * Creates a new proxy selector route planner.
     77      *
     78      * @param schreg    the scheme registry
     79      * @param prosel    the proxy selector, or
     80      *                  <code>null</code> for the system default
     81      */
     82     public ProxySelectorRoutePlanner(SchemeRegistry schreg,
     83                                      ProxySelector prosel) {
     84 
     85         if (schreg == null) {
     86             throw new IllegalArgumentException
     87                 ("SchemeRegistry must not be null.");
     88         }
     89         schemeRegistry = schreg;
     90         proxySelector  = prosel;
     91     }
     92 
     93 
     94     /**
     95      * Obtains the proxy selector to use.
     96      *
     97      * @return the proxy selector, or <code>null</code> for the system default
     98      */
     99     public ProxySelector getProxySelector() {
    100         return this.proxySelector;
    101     }
    102 
    103 
    104     /**
    105      * Sets the proxy selector to use.
    106      *
    107      * @param prosel    the proxy selector, or
    108      *                  <code>null</code> to use the system default
    109      */
    110     public void setProxySelector(ProxySelector prosel) {
    111         this.proxySelector = prosel;
    112     }
    113 
    114 
    115 
    116     // non-javadoc, see interface HttpRoutePlanner
    117     public HttpRoute determineRoute(HttpHost target,
    118                                     HttpRequest request,
    119                                     HttpContext context)
    120         throws HttpException {
    121 
    122         if (request == null) {
    123             throw new IllegalStateException
    124                 ("Request must not be null.");
    125         }
    126 
    127         // If we have a forced route, we can do without a target.
    128         HttpRoute route =
    129             ConnRouteParams.getForcedRoute(request.getParams());
    130         if (route != null)
    131             return route;
    132 
    133         // If we get here, there is no forced route.
    134         // So we need a target to compute a route.
    135 
    136         if (target == null) {
    137             throw new IllegalStateException
    138                 ("Target host must not be null.");
    139         }
    140 
    141         final InetAddress local =
    142             ConnRouteParams.getLocalAddress(request.getParams());
    143         final HttpHost proxy = determineProxy(target, request, context);
    144 
    145         final Scheme schm =
    146             this.schemeRegistry.getScheme(target.getSchemeName());
    147         // as it is typically used for TLS/SSL, we assume that
    148         // a layered scheme implies a secure connection
    149         final boolean secure = schm.isLayered();
    150 
    151         if (proxy == null) {
    152             route = new HttpRoute(target, local, secure);
    153         } else {
    154             route = new HttpRoute(target, local, proxy, secure);
    155         }
    156         return route;
    157     }
    158 
    159 
    160     /**
    161      * Determines a proxy for the given target.
    162      *
    163      * @param target    the planned target, never <code>null</code>
    164      * @param request   the request to be sent, never <code>null</code>
    165      * @param context   the context, or <code>null</code>
    166      *
    167      * @return  the proxy to use, or <code>null</code> for a direct route
    168      *
    169      * @throws HttpException
    170      *         in case of system proxy settings that cannot be handled
    171      */
    172     protected HttpHost determineProxy(HttpHost    target,
    173                                       HttpRequest request,
    174                                       HttpContext context)
    175         throws HttpException {
    176 
    177         // the proxy selector can be 'unset', so we better deal with null here
    178         ProxySelector psel = this.proxySelector;
    179         if (psel == null)
    180             psel = ProxySelector.getDefault();
    181         if (psel == null)
    182             return null;
    183 
    184         URI targetURI = null;
    185         try {
    186             targetURI = new URI(target.toURI());
    187         } catch (URISyntaxException usx) {
    188             throw new HttpException
    189                 ("Cannot convert host to URI: " + target, usx);
    190         }
    191         List<Proxy> proxies = psel.select(targetURI);
    192 
    193         Proxy p = chooseProxy(proxies, target, request, context);
    194 
    195         HttpHost result = null;
    196         if (p.type() == Proxy.Type.HTTP) {
    197             // convert the socket address to an HttpHost
    198             if (!(p.address() instanceof InetSocketAddress)) {
    199                 throw new HttpException
    200                     ("Unable to handle non-Inet proxy address: "+p.address());
    201             }
    202             final InetSocketAddress isa = (InetSocketAddress) p.address();
    203             // assume default scheme (http)
    204             result = new HttpHost(getHost(isa), isa.getPort());
    205         }
    206 
    207         return result;
    208     }
    209 
    210 
    211     /**
    212      * Obtains a host from an {@link InetSocketAddress}.
    213      *
    214      * @param isa       the socket address
    215      *
    216      * @return  a host string, either as a symbolic name or
    217      *          as a literal IP address string
    218      * <br/>
    219      * (TODO: determine format for IPv6 addresses, with or without [brackets])
    220      */
    221     protected String getHost(InetSocketAddress isa) {
    222 
    223         //@@@ Will this work with literal IPv6 addresses, or do we
    224         //@@@ need to wrap these in [] for the string representation?
    225         //@@@ Having it in this method at least allows for easy workarounds.
    226        return isa.isUnresolved() ?
    227             isa.getHostName() : isa.getAddress().getHostAddress();
    228 
    229     }
    230 
    231 
    232     /*
    233      * Chooses a proxy from a list of available proxies.
    234      * The default implementation just picks the first non-SOCKS proxy
    235      * from the list. If there are only SOCKS proxies,
    236      * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned.
    237      * Derived classes may implement more advanced strategies,
    238      * such as proxy rotation if there are multiple options.
    239      *
    240      * @param proxies   the list of proxies to choose from,
    241      *                  never <code>null</code> or empty
    242      * @param target    the planned target, never <code>null</code>
    243      * @param request   the request to be sent, never <code>null</code>
    244      * @param context   the context, or <code>null</code>
    245      *
    246      * @return  a proxy of type {@link Proxy.Type#DIRECT DIRECT}
    247      *          or {@link Proxy.Type#HTTP HTTP}, never <code>null</code>
    248      */
    249     protected Proxy chooseProxy(List<Proxy> proxies,
    250                                 HttpHost    target,
    251                                 HttpRequest request,
    252                                 HttpContext context) {
    253 
    254         if ((proxies == null) || proxies.isEmpty()) {
    255             throw new IllegalArgumentException
    256                 ("Proxy list must not be empty.");
    257         }
    258 
    259         Proxy result = null;
    260 
    261         // check the list for one we can use
    262         for (int i=0; (result == null) && (i < proxies.size()); i++) {
    263 
    264             Proxy p = proxies.get(i);
    265             switch (p.type()) {
    266 
    267             case DIRECT:
    268             case HTTP:
    269                 result = p;
    270                 break;
    271 
    272             case SOCKS:
    273                 // SOCKS hosts are not handled on the route level.
    274                 // The socket may make use of the SOCKS host though.
    275                 break;
    276             }
    277         }
    278 
    279         if (result == null) {
    280             //@@@ log as warning or info that only a socks proxy is available?
    281             // result can only be null if all proxies are socks proxies
    282             // socks proxies are not handled on the route planning level
    283             result = Proxy.NO_PROXY;
    284         }
    285 
    286         return result;
    287     }
    288 
    289 } // class ProxySelectorRoutePlanner
    290 
    291