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