|
1 /* |
|
2 * ==================================================================== |
|
3 * Licensed to the Apache Software Foundation (ASF) under one |
|
4 * or more contributor license agreements. See the NOTICE file |
|
5 * distributed with this work for additional information |
|
6 * regarding copyright ownership. The ASF licenses this file |
|
7 * to you under the Apache License, Version 2.0 (the |
|
8 * "License"); you may not use this file except in compliance |
|
9 * with the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, |
|
14 * software distributed under the License is distributed on an |
|
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
16 * KIND, either express or implied. See the License for the |
|
17 * specific language governing permissions and limitations |
|
18 * under the License. |
|
19 * ==================================================================== |
|
20 * |
|
21 * This software consists of voluntary contributions made by many |
|
22 * individuals on behalf of the Apache Software Foundation. For more |
|
23 * information on the Apache Software Foundation, please see |
|
24 * <http://www.apache.org/>. |
|
25 * |
|
26 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.impl.conn; |
|
29 |
|
30 |
|
31 import java.net.InetAddress; |
|
32 import java.net.InetSocketAddress; |
|
33 import java.net.Proxy; |
|
34 import java.net.ProxySelector; |
|
35 import java.net.URI; |
|
36 import java.net.URISyntaxException; |
|
37 import java.util.List; |
|
38 |
|
39 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; |
|
40 import ch.boye.httpclientandroidlib.HttpException; |
|
41 import ch.boye.httpclientandroidlib.HttpHost; |
|
42 import ch.boye.httpclientandroidlib.HttpRequest; |
|
43 import ch.boye.httpclientandroidlib.protocol.HttpContext; |
|
44 |
|
45 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; |
|
46 import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; |
|
47 import ch.boye.httpclientandroidlib.conn.scheme.Scheme; |
|
48 import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; |
|
49 |
|
50 import ch.boye.httpclientandroidlib.conn.params.ConnRouteParams; |
|
51 |
|
52 |
|
53 /** |
|
54 * Default implementation of an {@link HttpRoutePlanner}. |
|
55 * This implementation is based on {@link java.net.ProxySelector}. |
|
56 * By default, it will pick up the proxy settings of the JVM, either |
|
57 * from system properties or from the browser running the application. |
|
58 * Additionally, it interprets some |
|
59 * {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames parameters}, |
|
60 * though not the {@link |
|
61 * ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. |
|
62 * <p> |
|
63 * The following parameters can be used to customize the behavior of this |
|
64 * class: |
|
65 * <ul> |
|
66 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> |
|
67 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> |
|
68 * </ul> |
|
69 * |
|
70 * @since 4.0 |
|
71 */ |
|
72 @NotThreadSafe // e.g [gs]etProxySelector() |
|
73 public class ProxySelectorRoutePlanner implements HttpRoutePlanner { |
|
74 |
|
75 /** The scheme registry. */ |
|
76 protected final SchemeRegistry schemeRegistry; // @ThreadSafe |
|
77 |
|
78 /** The proxy selector to use, or <code>null</code> for system default. */ |
|
79 protected ProxySelector proxySelector; |
|
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 * Obtains the proxy selector to use. |
|
101 * |
|
102 * @return the proxy selector, or <code>null</code> for the system default |
|
103 */ |
|
104 public ProxySelector getProxySelector() { |
|
105 return this.proxySelector; |
|
106 } |
|
107 |
|
108 /** |
|
109 * Sets the proxy selector to use. |
|
110 * |
|
111 * @param prosel the proxy selector, or |
|
112 * <code>null</code> to use the system default |
|
113 */ |
|
114 public void setProxySelector(ProxySelector prosel) { |
|
115 this.proxySelector = prosel; |
|
116 } |
|
117 |
|
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 final HttpHost proxy = determineProxy(target, request, context); |
|
145 |
|
146 final Scheme schm = |
|
147 this.schemeRegistry.getScheme(target.getSchemeName()); |
|
148 // as it is typically used for TLS/SSL, we assume that |
|
149 // a layered scheme implies a secure connection |
|
150 final boolean secure = schm.isLayered(); |
|
151 |
|
152 if (proxy == null) { |
|
153 route = new HttpRoute(target, local, secure); |
|
154 } else { |
|
155 route = new HttpRoute(target, local, proxy, secure); |
|
156 } |
|
157 return route; |
|
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 * Obtains a host from an {@link InetSocketAddress}. |
|
212 * |
|
213 * @param isa the socket address |
|
214 * |
|
215 * @return a host string, either as a symbolic name or |
|
216 * as a literal IP address string |
|
217 * <br/> |
|
218 * (TODO: determine format for IPv6 addresses, with or without [brackets]) |
|
219 */ |
|
220 protected String getHost(InetSocketAddress isa) { |
|
221 |
|
222 //@@@ Will this work with literal IPv6 addresses, or do we |
|
223 //@@@ need to wrap these in [] for the string representation? |
|
224 //@@@ Having it in this method at least allows for easy workarounds. |
|
225 return isa.isUnresolved() ? |
|
226 isa.getHostName() : isa.getAddress().getHostAddress(); |
|
227 |
|
228 } |
|
229 |
|
230 /** |
|
231 * Chooses a proxy from a list of available proxies. |
|
232 * The default implementation just picks the first non-SOCKS proxy |
|
233 * from the list. If there are only SOCKS proxies, |
|
234 * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. |
|
235 * Derived classes may implement more advanced strategies, |
|
236 * such as proxy rotation if there are multiple options. |
|
237 * |
|
238 * @param proxies the list of proxies to choose from, |
|
239 * never <code>null</code> or empty |
|
240 * @param target the planned target, never <code>null</code> |
|
241 * @param request the request to be sent, never <code>null</code> |
|
242 * @param context the context, or <code>null</code> |
|
243 * |
|
244 * @return a proxy type |
|
245 */ |
|
246 protected Proxy chooseProxy(List<Proxy> proxies, |
|
247 HttpHost target, |
|
248 HttpRequest request, |
|
249 HttpContext context) { |
|
250 |
|
251 if ((proxies == null) || proxies.isEmpty()) { |
|
252 throw new IllegalArgumentException |
|
253 ("Proxy list must not be empty."); |
|
254 } |
|
255 |
|
256 Proxy result = null; |
|
257 |
|
258 // check the list for one we can use |
|
259 for (int i=0; (result == null) && (i < proxies.size()); i++) { |
|
260 |
|
261 Proxy p = proxies.get(i); |
|
262 switch (p.type()) { |
|
263 |
|
264 case DIRECT: |
|
265 case HTTP: |
|
266 result = p; |
|
267 break; |
|
268 |
|
269 case SOCKS: |
|
270 // SOCKS hosts are not handled on the route level. |
|
271 // The socket may make use of the SOCKS host though. |
|
272 break; |
|
273 } |
|
274 } |
|
275 |
|
276 if (result == null) { |
|
277 //@@@ log as warning or info that only a socks proxy is available? |
|
278 // result can only be null if all proxies are socks proxies |
|
279 // socks proxies are not handled on the route planning level |
|
280 result = Proxy.NO_PROXY; |
|
281 } |
|
282 |
|
283 return result; |
|
284 } |
|
285 |
|
286 } |
|
287 |