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 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      */
     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      */
    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      */
    111     public void setProxySelector(ProxySelector prosel) {
    112         this.proxySelector = prosel;
    113     }
    114 
    115 
    116 
    117     // non-javadoc, see interface HttpRoutePlanner
    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      */
    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      */
    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      */
    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