|
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.client; |
|
29 |
|
30 import java.io.IOException; |
|
31 import java.io.InterruptedIOException; |
|
32 import java.net.URI; |
|
33 import java.net.URISyntaxException; |
|
34 import java.util.Locale; |
|
35 import java.util.Map; |
|
36 import java.util.concurrent.TimeUnit; |
|
37 |
|
38 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; |
|
39 |
|
40 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; |
|
41 /* LogFactory removed by HttpClient for Android script. */ |
|
42 import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; |
|
43 import ch.boye.httpclientandroidlib.Header; |
|
44 import ch.boye.httpclientandroidlib.HttpEntity; |
|
45 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; |
|
46 import ch.boye.httpclientandroidlib.HttpException; |
|
47 import ch.boye.httpclientandroidlib.HttpHost; |
|
48 import ch.boye.httpclientandroidlib.HttpRequest; |
|
49 import ch.boye.httpclientandroidlib.HttpResponse; |
|
50 import ch.boye.httpclientandroidlib.ProtocolException; |
|
51 import ch.boye.httpclientandroidlib.ProtocolVersion; |
|
52 import ch.boye.httpclientandroidlib.auth.AuthScheme; |
|
53 import ch.boye.httpclientandroidlib.auth.AuthScope; |
|
54 import ch.boye.httpclientandroidlib.auth.AuthState; |
|
55 import ch.boye.httpclientandroidlib.auth.AuthenticationException; |
|
56 import ch.boye.httpclientandroidlib.auth.Credentials; |
|
57 import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; |
|
58 import ch.boye.httpclientandroidlib.client.AuthenticationHandler; |
|
59 import ch.boye.httpclientandroidlib.client.RedirectStrategy; |
|
60 import ch.boye.httpclientandroidlib.client.RequestDirector; |
|
61 import ch.boye.httpclientandroidlib.client.CredentialsProvider; |
|
62 import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; |
|
63 import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; |
|
64 import ch.boye.httpclientandroidlib.client.RedirectException; |
|
65 import ch.boye.httpclientandroidlib.client.UserTokenHandler; |
|
66 import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest; |
|
67 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; |
|
68 import ch.boye.httpclientandroidlib.client.params.ClientPNames; |
|
69 import ch.boye.httpclientandroidlib.client.params.HttpClientParams; |
|
70 import ch.boye.httpclientandroidlib.client.protocol.ClientContext; |
|
71 import ch.boye.httpclientandroidlib.client.utils.URIUtils; |
|
72 import ch.boye.httpclientandroidlib.conn.BasicManagedEntity; |
|
73 import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; |
|
74 import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; |
|
75 import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; |
|
76 import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; |
|
77 import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams; |
|
78 import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector; |
|
79 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; |
|
80 import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector; |
|
81 import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; |
|
82 import ch.boye.httpclientandroidlib.conn.scheme.Scheme; |
|
83 import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; |
|
84 import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; |
|
85 import ch.boye.httpclientandroidlib.message.BasicHttpRequest; |
|
86 import ch.boye.httpclientandroidlib.params.HttpConnectionParams; |
|
87 import ch.boye.httpclientandroidlib.params.HttpParams; |
|
88 import ch.boye.httpclientandroidlib.params.HttpProtocolParams; |
|
89 import ch.boye.httpclientandroidlib.protocol.ExecutionContext; |
|
90 import ch.boye.httpclientandroidlib.protocol.HttpContext; |
|
91 import ch.boye.httpclientandroidlib.protocol.HttpProcessor; |
|
92 import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; |
|
93 import ch.boye.httpclientandroidlib.util.EntityUtils; |
|
94 |
|
95 /** |
|
96 * Default implementation of {@link RequestDirector}. |
|
97 * <p> |
|
98 * The following parameters can be used to customize the behavior of this |
|
99 * class: |
|
100 * <ul> |
|
101 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> |
|
102 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> |
|
103 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> |
|
104 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> |
|
105 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> |
|
106 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> |
|
107 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> |
|
108 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> |
|
109 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> |
|
110 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> |
|
111 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> |
|
112 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> |
|
113 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> |
|
114 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> |
|
115 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> |
|
116 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> |
|
117 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> |
|
118 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> |
|
119 * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> |
|
120 * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> |
|
121 * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> |
|
122 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> |
|
123 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> |
|
124 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> |
|
125 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> |
|
126 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> |
|
127 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> |
|
128 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> |
|
129 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> |
|
130 * </ul> |
|
131 * |
|
132 * @since 4.0 |
|
133 */ |
|
134 @SuppressWarnings("deprecation") |
|
135 @NotThreadSafe // e.g. managedConn |
|
136 public class DefaultRequestDirector implements RequestDirector { |
|
137 |
|
138 public HttpClientAndroidLog log; |
|
139 |
|
140 /** The connection manager. */ |
|
141 protected final ClientConnectionManager connManager; |
|
142 |
|
143 /** The route planner. */ |
|
144 protected final HttpRoutePlanner routePlanner; |
|
145 |
|
146 /** The connection re-use strategy. */ |
|
147 protected final ConnectionReuseStrategy reuseStrategy; |
|
148 |
|
149 /** The keep-alive duration strategy. */ |
|
150 protected final ConnectionKeepAliveStrategy keepAliveStrategy; |
|
151 |
|
152 /** The request executor. */ |
|
153 protected final HttpRequestExecutor requestExec; |
|
154 |
|
155 /** The HTTP protocol processor. */ |
|
156 protected final HttpProcessor httpProcessor; |
|
157 |
|
158 /** The request retry handler. */ |
|
159 protected final HttpRequestRetryHandler retryHandler; |
|
160 |
|
161 /** The redirect handler. */ |
|
162 @Deprecated |
|
163 protected final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler = null; |
|
164 |
|
165 /** The redirect strategy. */ |
|
166 protected final RedirectStrategy redirectStrategy; |
|
167 |
|
168 /** The target authentication handler. */ |
|
169 protected final AuthenticationHandler targetAuthHandler; |
|
170 |
|
171 /** The proxy authentication handler. */ |
|
172 protected final AuthenticationHandler proxyAuthHandler; |
|
173 |
|
174 /** The user token handler. */ |
|
175 protected final UserTokenHandler userTokenHandler; |
|
176 |
|
177 /** The HTTP parameters. */ |
|
178 protected final HttpParams params; |
|
179 |
|
180 /** The currently allocated connection. */ |
|
181 protected ManagedClientConnection managedConn; |
|
182 |
|
183 protected final AuthState targetAuthState; |
|
184 |
|
185 protected final AuthState proxyAuthState; |
|
186 |
|
187 private int execCount; |
|
188 |
|
189 private int redirectCount; |
|
190 |
|
191 private int maxRedirects; |
|
192 |
|
193 private HttpHost virtualHost; |
|
194 |
|
195 @Deprecated |
|
196 public DefaultRequestDirector( |
|
197 final HttpRequestExecutor requestExec, |
|
198 final ClientConnectionManager conman, |
|
199 final ConnectionReuseStrategy reustrat, |
|
200 final ConnectionKeepAliveStrategy kastrat, |
|
201 final HttpRoutePlanner rouplan, |
|
202 final HttpProcessor httpProcessor, |
|
203 final HttpRequestRetryHandler retryHandler, |
|
204 final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler, |
|
205 final AuthenticationHandler targetAuthHandler, |
|
206 final AuthenticationHandler proxyAuthHandler, |
|
207 final UserTokenHandler userTokenHandler, |
|
208 final HttpParams params) { |
|
209 this(new HttpClientAndroidLog(DefaultRequestDirector.class), |
|
210 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, |
|
211 new DefaultRedirectStrategyAdaptor(redirectHandler), |
|
212 targetAuthHandler, proxyAuthHandler, userTokenHandler, params); |
|
213 } |
|
214 |
|
215 |
|
216 /** |
|
217 * @since 4.1 |
|
218 */ |
|
219 public DefaultRequestDirector( |
|
220 final HttpClientAndroidLog log, |
|
221 final HttpRequestExecutor requestExec, |
|
222 final ClientConnectionManager conman, |
|
223 final ConnectionReuseStrategy reustrat, |
|
224 final ConnectionKeepAliveStrategy kastrat, |
|
225 final HttpRoutePlanner rouplan, |
|
226 final HttpProcessor httpProcessor, |
|
227 final HttpRequestRetryHandler retryHandler, |
|
228 final RedirectStrategy redirectStrategy, |
|
229 final AuthenticationHandler targetAuthHandler, |
|
230 final AuthenticationHandler proxyAuthHandler, |
|
231 final UserTokenHandler userTokenHandler, |
|
232 final HttpParams params) { |
|
233 |
|
234 if (log == null) { |
|
235 throw new IllegalArgumentException |
|
236 ("Log may not be null."); |
|
237 } |
|
238 if (requestExec == null) { |
|
239 throw new IllegalArgumentException |
|
240 ("Request executor may not be null."); |
|
241 } |
|
242 if (conman == null) { |
|
243 throw new IllegalArgumentException |
|
244 ("Client connection manager may not be null."); |
|
245 } |
|
246 if (reustrat == null) { |
|
247 throw new IllegalArgumentException |
|
248 ("Connection reuse strategy may not be null."); |
|
249 } |
|
250 if (kastrat == null) { |
|
251 throw new IllegalArgumentException |
|
252 ("Connection keep alive strategy may not be null."); |
|
253 } |
|
254 if (rouplan == null) { |
|
255 throw new IllegalArgumentException |
|
256 ("Route planner may not be null."); |
|
257 } |
|
258 if (httpProcessor == null) { |
|
259 throw new IllegalArgumentException |
|
260 ("HTTP protocol processor may not be null."); |
|
261 } |
|
262 if (retryHandler == null) { |
|
263 throw new IllegalArgumentException |
|
264 ("HTTP request retry handler may not be null."); |
|
265 } |
|
266 if (redirectStrategy == null) { |
|
267 throw new IllegalArgumentException |
|
268 ("Redirect strategy may not be null."); |
|
269 } |
|
270 if (targetAuthHandler == null) { |
|
271 throw new IllegalArgumentException |
|
272 ("Target authentication handler may not be null."); |
|
273 } |
|
274 if (proxyAuthHandler == null) { |
|
275 throw new IllegalArgumentException |
|
276 ("Proxy authentication handler may not be null."); |
|
277 } |
|
278 if (userTokenHandler == null) { |
|
279 throw new IllegalArgumentException |
|
280 ("User token handler may not be null."); |
|
281 } |
|
282 if (params == null) { |
|
283 throw new IllegalArgumentException |
|
284 ("HTTP parameters may not be null"); |
|
285 } |
|
286 this.log = log; |
|
287 this.requestExec = requestExec; |
|
288 this.connManager = conman; |
|
289 this.reuseStrategy = reustrat; |
|
290 this.keepAliveStrategy = kastrat; |
|
291 this.routePlanner = rouplan; |
|
292 this.httpProcessor = httpProcessor; |
|
293 this.retryHandler = retryHandler; |
|
294 this.redirectStrategy = redirectStrategy; |
|
295 this.targetAuthHandler = targetAuthHandler; |
|
296 this.proxyAuthHandler = proxyAuthHandler; |
|
297 this.userTokenHandler = userTokenHandler; |
|
298 this.params = params; |
|
299 |
|
300 this.managedConn = null; |
|
301 |
|
302 this.execCount = 0; |
|
303 this.redirectCount = 0; |
|
304 this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); |
|
305 this.targetAuthState = new AuthState(); |
|
306 this.proxyAuthState = new AuthState(); |
|
307 } // constructor |
|
308 |
|
309 |
|
310 private RequestWrapper wrapRequest( |
|
311 final HttpRequest request) throws ProtocolException { |
|
312 if (request instanceof HttpEntityEnclosingRequest) { |
|
313 return new EntityEnclosingRequestWrapper( |
|
314 (HttpEntityEnclosingRequest) request); |
|
315 } else { |
|
316 return new RequestWrapper( |
|
317 request); |
|
318 } |
|
319 } |
|
320 |
|
321 |
|
322 protected void rewriteRequestURI( |
|
323 final RequestWrapper request, |
|
324 final HttpRoute route) throws ProtocolException { |
|
325 try { |
|
326 |
|
327 URI uri = request.getURI(); |
|
328 if (route.getProxyHost() != null && !route.isTunnelled()) { |
|
329 // Make sure the request URI is absolute |
|
330 if (!uri.isAbsolute()) { |
|
331 HttpHost target = route.getTargetHost(); |
|
332 uri = URIUtils.rewriteURI(uri, target); |
|
333 request.setURI(uri); |
|
334 } |
|
335 } else { |
|
336 // Make sure the request URI is relative |
|
337 if (uri.isAbsolute()) { |
|
338 uri = URIUtils.rewriteURI(uri, null); |
|
339 request.setURI(uri); |
|
340 } |
|
341 } |
|
342 |
|
343 } catch (URISyntaxException ex) { |
|
344 throw new ProtocolException("Invalid URI: " + |
|
345 request.getRequestLine().getUri(), ex); |
|
346 } |
|
347 } |
|
348 |
|
349 |
|
350 // non-javadoc, see interface ClientRequestDirector |
|
351 public HttpResponse execute(HttpHost target, HttpRequest request, |
|
352 HttpContext context) |
|
353 throws HttpException, IOException { |
|
354 |
|
355 HttpRequest orig = request; |
|
356 RequestWrapper origWrapper = wrapRequest(orig); |
|
357 origWrapper.setParams(params); |
|
358 HttpRoute origRoute = determineRoute(target, origWrapper, context); |
|
359 |
|
360 virtualHost = (HttpHost) orig.getParams().getParameter( |
|
361 ClientPNames.VIRTUAL_HOST); |
|
362 |
|
363 // HTTPCLIENT-1092 - add the port if necessary |
|
364 if (virtualHost != null && virtualHost.getPort() == -1) |
|
365 { |
|
366 int port = target.getPort(); |
|
367 if (port != -1){ |
|
368 virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName()); |
|
369 } |
|
370 } |
|
371 |
|
372 RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); |
|
373 |
|
374 boolean reuse = false; |
|
375 boolean done = false; |
|
376 try { |
|
377 HttpResponse response = null; |
|
378 while (!done) { |
|
379 // In this loop, the RoutedRequest may be replaced by a |
|
380 // followup request and route. The request and route passed |
|
381 // in the method arguments will be replaced. The original |
|
382 // request is still available in 'orig'. |
|
383 |
|
384 RequestWrapper wrapper = roureq.getRequest(); |
|
385 HttpRoute route = roureq.getRoute(); |
|
386 response = null; |
|
387 |
|
388 // See if we have a user token bound to the execution context |
|
389 Object userToken = context.getAttribute(ClientContext.USER_TOKEN); |
|
390 |
|
391 // Allocate connection if needed |
|
392 if (managedConn == null) { |
|
393 ClientConnectionRequest connRequest = connManager.requestConnection( |
|
394 route, userToken); |
|
395 if (orig instanceof AbortableHttpRequest) { |
|
396 ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); |
|
397 } |
|
398 |
|
399 long timeout = ConnManagerParams.getTimeout(params); |
|
400 try { |
|
401 managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); |
|
402 } catch(InterruptedException interrupted) { |
|
403 InterruptedIOException iox = new InterruptedIOException(); |
|
404 iox.initCause(interrupted); |
|
405 throw iox; |
|
406 } |
|
407 |
|
408 if (HttpConnectionParams.isStaleCheckingEnabled(params)) { |
|
409 // validate connection |
|
410 if (managedConn.isOpen()) { |
|
411 this.log.debug("Stale connection check"); |
|
412 if (managedConn.isStale()) { |
|
413 this.log.debug("Stale connection detected"); |
|
414 managedConn.close(); |
|
415 } |
|
416 } |
|
417 } |
|
418 } |
|
419 |
|
420 if (orig instanceof AbortableHttpRequest) { |
|
421 ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); |
|
422 } |
|
423 |
|
424 try { |
|
425 tryConnect(roureq, context); |
|
426 } catch (TunnelRefusedException ex) { |
|
427 if (this.log.isDebugEnabled()) { |
|
428 this.log.debug(ex.getMessage()); |
|
429 } |
|
430 response = ex.getResponse(); |
|
431 break; |
|
432 } |
|
433 |
|
434 // Reset headers on the request wrapper |
|
435 wrapper.resetHeaders(); |
|
436 |
|
437 // Re-write request URI if needed |
|
438 rewriteRequestURI(wrapper, route); |
|
439 |
|
440 // Use virtual host if set |
|
441 target = virtualHost; |
|
442 |
|
443 if (target == null) { |
|
444 target = route.getTargetHost(); |
|
445 } |
|
446 |
|
447 HttpHost proxy = route.getProxyHost(); |
|
448 |
|
449 // Populate the execution context |
|
450 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, |
|
451 target); |
|
452 context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, |
|
453 proxy); |
|
454 context.setAttribute(ExecutionContext.HTTP_CONNECTION, |
|
455 managedConn); |
|
456 context.setAttribute(ClientContext.TARGET_AUTH_STATE, |
|
457 targetAuthState); |
|
458 context.setAttribute(ClientContext.PROXY_AUTH_STATE, |
|
459 proxyAuthState); |
|
460 |
|
461 // Run request protocol interceptors |
|
462 requestExec.preProcess(wrapper, httpProcessor, context); |
|
463 |
|
464 response = tryExecute(roureq, context); |
|
465 if (response == null) { |
|
466 // Need to start over |
|
467 continue; |
|
468 } |
|
469 |
|
470 // Run response protocol interceptors |
|
471 response.setParams(params); |
|
472 requestExec.postProcess(response, httpProcessor, context); |
|
473 |
|
474 |
|
475 // The connection is in or can be brought to a re-usable state. |
|
476 reuse = reuseStrategy.keepAlive(response, context); |
|
477 if (reuse) { |
|
478 // Set the idle duration of this connection |
|
479 long duration = keepAliveStrategy.getKeepAliveDuration(response, context); |
|
480 if (this.log.isDebugEnabled()) { |
|
481 String s; |
|
482 if (duration > 0) { |
|
483 s = "for " + duration + " " + TimeUnit.MILLISECONDS; |
|
484 } else { |
|
485 s = "indefinitely"; |
|
486 } |
|
487 this.log.debug("Connection can be kept alive " + s); |
|
488 } |
|
489 managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); |
|
490 } |
|
491 |
|
492 RoutedRequest followup = handleResponse(roureq, response, context); |
|
493 if (followup == null) { |
|
494 done = true; |
|
495 } else { |
|
496 if (reuse) { |
|
497 // Make sure the response body is fully consumed, if present |
|
498 HttpEntity entity = response.getEntity(); |
|
499 EntityUtils.consume(entity); |
|
500 // entity consumed above is not an auto-release entity, |
|
501 // need to mark the connection re-usable explicitly |
|
502 managedConn.markReusable(); |
|
503 } else { |
|
504 managedConn.close(); |
|
505 invalidateAuthIfSuccessful(this.proxyAuthState); |
|
506 invalidateAuthIfSuccessful(this.targetAuthState); |
|
507 } |
|
508 // check if we can use the same connection for the followup |
|
509 if (!followup.getRoute().equals(roureq.getRoute())) { |
|
510 releaseConnection(); |
|
511 } |
|
512 roureq = followup; |
|
513 } |
|
514 |
|
515 if (managedConn != null && userToken == null) { |
|
516 userToken = userTokenHandler.getUserToken(context); |
|
517 context.setAttribute(ClientContext.USER_TOKEN, userToken); |
|
518 if (userToken != null) { |
|
519 managedConn.setState(userToken); |
|
520 } |
|
521 } |
|
522 |
|
523 } // while not done |
|
524 |
|
525 |
|
526 // check for entity, release connection if possible |
|
527 if ((response == null) || (response.getEntity() == null) || |
|
528 !response.getEntity().isStreaming()) { |
|
529 // connection not needed and (assumed to be) in re-usable state |
|
530 if (reuse) |
|
531 managedConn.markReusable(); |
|
532 releaseConnection(); |
|
533 } else { |
|
534 // install an auto-release entity |
|
535 HttpEntity entity = response.getEntity(); |
|
536 entity = new BasicManagedEntity(entity, managedConn, reuse); |
|
537 response.setEntity(entity); |
|
538 } |
|
539 |
|
540 return response; |
|
541 |
|
542 } catch (ConnectionShutdownException ex) { |
|
543 InterruptedIOException ioex = new InterruptedIOException( |
|
544 "Connection has been shut down"); |
|
545 ioex.initCause(ex); |
|
546 throw ioex; |
|
547 } catch (HttpException ex) { |
|
548 abortConnection(); |
|
549 throw ex; |
|
550 } catch (IOException ex) { |
|
551 abortConnection(); |
|
552 throw ex; |
|
553 } catch (RuntimeException ex) { |
|
554 abortConnection(); |
|
555 throw ex; |
|
556 } |
|
557 } // execute |
|
558 |
|
559 /** |
|
560 * Establish connection either directly or through a tunnel and retry in case of |
|
561 * a recoverable I/O failure |
|
562 */ |
|
563 private void tryConnect( |
|
564 final RoutedRequest req, final HttpContext context) throws HttpException, IOException { |
|
565 HttpRoute route = req.getRoute(); |
|
566 |
|
567 int connectCount = 0; |
|
568 for (;;) { |
|
569 // Increment connect count |
|
570 connectCount++; |
|
571 try { |
|
572 if (!managedConn.isOpen()) { |
|
573 managedConn.open(route, context, params); |
|
574 } else { |
|
575 managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params)); |
|
576 } |
|
577 establishRoute(route, context); |
|
578 break; |
|
579 } catch (IOException ex) { |
|
580 try { |
|
581 managedConn.close(); |
|
582 } catch (IOException ignore) { |
|
583 } |
|
584 if (retryHandler.retryRequest(ex, connectCount, context)) { |
|
585 if (this.log.isInfoEnabled()) { |
|
586 this.log.info("I/O exception ("+ ex.getClass().getName() + |
|
587 ") caught when connecting to the target host: " |
|
588 + ex.getMessage()); |
|
589 } |
|
590 if (this.log.isDebugEnabled()) { |
|
591 this.log.debug(ex.getMessage(), ex); |
|
592 } |
|
593 this.log.info("Retrying connect"); |
|
594 } else { |
|
595 throw ex; |
|
596 } |
|
597 } |
|
598 } |
|
599 } |
|
600 |
|
601 /** |
|
602 * Execute request and retry in case of a recoverable I/O failure |
|
603 */ |
|
604 private HttpResponse tryExecute( |
|
605 final RoutedRequest req, final HttpContext context) throws HttpException, IOException { |
|
606 RequestWrapper wrapper = req.getRequest(); |
|
607 HttpRoute route = req.getRoute(); |
|
608 HttpResponse response = null; |
|
609 |
|
610 Exception retryReason = null; |
|
611 for (;;) { |
|
612 // Increment total exec count (with redirects) |
|
613 execCount++; |
|
614 // Increment exec count for this particular request |
|
615 wrapper.incrementExecCount(); |
|
616 if (!wrapper.isRepeatable()) { |
|
617 this.log.debug("Cannot retry non-repeatable request"); |
|
618 if (retryReason != null) { |
|
619 throw new NonRepeatableRequestException("Cannot retry request " + |
|
620 "with a non-repeatable request entity. The cause lists the " + |
|
621 "reason the original request failed.", retryReason); |
|
622 } else { |
|
623 throw new NonRepeatableRequestException("Cannot retry request " + |
|
624 "with a non-repeatable request entity."); |
|
625 } |
|
626 } |
|
627 |
|
628 try { |
|
629 if (!managedConn.isOpen()) { |
|
630 // If we have a direct route to the target host |
|
631 // just re-open connection and re-try the request |
|
632 if (!route.isTunnelled()) { |
|
633 this.log.debug("Reopening the direct connection."); |
|
634 managedConn.open(route, context, params); |
|
635 } else { |
|
636 // otherwise give up |
|
637 this.log.debug("Proxied connection. Need to start over."); |
|
638 break; |
|
639 } |
|
640 } |
|
641 |
|
642 if (this.log.isDebugEnabled()) { |
|
643 this.log.debug("Attempt " + execCount + " to execute request"); |
|
644 } |
|
645 response = requestExec.execute(wrapper, managedConn, context); |
|
646 break; |
|
647 |
|
648 } catch (IOException ex) { |
|
649 this.log.debug("Closing the connection."); |
|
650 try { |
|
651 managedConn.close(); |
|
652 } catch (IOException ignore) { |
|
653 } |
|
654 if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) { |
|
655 if (this.log.isInfoEnabled()) { |
|
656 this.log.info("I/O exception ("+ ex.getClass().getName() + |
|
657 ") caught when processing request: " |
|
658 + ex.getMessage()); |
|
659 } |
|
660 if (this.log.isDebugEnabled()) { |
|
661 this.log.debug(ex.getMessage(), ex); |
|
662 } |
|
663 this.log.info("Retrying request"); |
|
664 retryReason = ex; |
|
665 } else { |
|
666 throw ex; |
|
667 } |
|
668 } |
|
669 } |
|
670 return response; |
|
671 } |
|
672 |
|
673 /** |
|
674 * Returns the connection back to the connection manager |
|
675 * and prepares for retrieving a new connection during |
|
676 * the next request. |
|
677 */ |
|
678 protected void releaseConnection() { |
|
679 // Release the connection through the ManagedConnection instead of the |
|
680 // ConnectionManager directly. This lets the connection control how |
|
681 // it is released. |
|
682 try { |
|
683 managedConn.releaseConnection(); |
|
684 } catch(IOException ignored) { |
|
685 this.log.debug("IOException releasing connection", ignored); |
|
686 } |
|
687 managedConn = null; |
|
688 } |
|
689 |
|
690 /** |
|
691 * Determines the route for a request. |
|
692 * Called by {@link #execute} |
|
693 * to determine the route for either the original or a followup request. |
|
694 * |
|
695 * @param target the target host for the request. |
|
696 * Implementations may accept <code>null</code> |
|
697 * if they can still determine a route, for example |
|
698 * to a default target or by inspecting the request. |
|
699 * @param request the request to execute |
|
700 * @param context the context to use for the execution, |
|
701 * never <code>null</code> |
|
702 * |
|
703 * @return the route the request should take |
|
704 * |
|
705 * @throws HttpException in case of a problem |
|
706 */ |
|
707 protected HttpRoute determineRoute(HttpHost target, |
|
708 HttpRequest request, |
|
709 HttpContext context) |
|
710 throws HttpException { |
|
711 |
|
712 if (target == null) { |
|
713 target = (HttpHost) request.getParams().getParameter( |
|
714 ClientPNames.DEFAULT_HOST); |
|
715 } |
|
716 if (target == null) { |
|
717 throw new IllegalStateException |
|
718 ("Target host must not be null, or set in parameters."); |
|
719 } |
|
720 |
|
721 return this.routePlanner.determineRoute(target, request, context); |
|
722 } |
|
723 |
|
724 |
|
725 /** |
|
726 * Establishes the target route. |
|
727 * |
|
728 * @param route the route to establish |
|
729 * @param context the context for the request execution |
|
730 * |
|
731 * @throws HttpException in case of a problem |
|
732 * @throws IOException in case of an IO problem |
|
733 */ |
|
734 protected void establishRoute(HttpRoute route, HttpContext context) |
|
735 throws HttpException, IOException { |
|
736 |
|
737 HttpRouteDirector rowdy = new BasicRouteDirector(); |
|
738 int step; |
|
739 do { |
|
740 HttpRoute fact = managedConn.getRoute(); |
|
741 step = rowdy.nextStep(route, fact); |
|
742 |
|
743 switch (step) { |
|
744 |
|
745 case HttpRouteDirector.CONNECT_TARGET: |
|
746 case HttpRouteDirector.CONNECT_PROXY: |
|
747 managedConn.open(route, context, this.params); |
|
748 break; |
|
749 |
|
750 case HttpRouteDirector.TUNNEL_TARGET: { |
|
751 boolean secure = createTunnelToTarget(route, context); |
|
752 this.log.debug("Tunnel to target created."); |
|
753 managedConn.tunnelTarget(secure, this.params); |
|
754 } break; |
|
755 |
|
756 case HttpRouteDirector.TUNNEL_PROXY: { |
|
757 // The most simple example for this case is a proxy chain |
|
758 // of two proxies, where P1 must be tunnelled to P2. |
|
759 // route: Source -> P1 -> P2 -> Target (3 hops) |
|
760 // fact: Source -> P1 -> Target (2 hops) |
|
761 final int hop = fact.getHopCount()-1; // the hop to establish |
|
762 boolean secure = createTunnelToProxy(route, hop, context); |
|
763 this.log.debug("Tunnel to proxy created."); |
|
764 managedConn.tunnelProxy(route.getHopTarget(hop), |
|
765 secure, this.params); |
|
766 } break; |
|
767 |
|
768 |
|
769 case HttpRouteDirector.LAYER_PROTOCOL: |
|
770 managedConn.layerProtocol(context, this.params); |
|
771 break; |
|
772 |
|
773 case HttpRouteDirector.UNREACHABLE: |
|
774 throw new HttpException("Unable to establish route: " + |
|
775 "planned = " + route + "; current = " + fact); |
|
776 case HttpRouteDirector.COMPLETE: |
|
777 // do nothing |
|
778 break; |
|
779 default: |
|
780 throw new IllegalStateException("Unknown step indicator " |
|
781 + step + " from RouteDirector."); |
|
782 } |
|
783 |
|
784 } while (step > HttpRouteDirector.COMPLETE); |
|
785 |
|
786 } // establishConnection |
|
787 |
|
788 |
|
789 /** |
|
790 * Creates a tunnel to the target server. |
|
791 * The connection must be established to the (last) proxy. |
|
792 * A CONNECT request for tunnelling through the proxy will |
|
793 * be created and sent, the response received and checked. |
|
794 * This method does <i>not</i> update the connection with |
|
795 * information about the tunnel, that is left to the caller. |
|
796 * |
|
797 * @param route the route to establish |
|
798 * @param context the context for request execution |
|
799 * |
|
800 * @return <code>true</code> if the tunnelled route is secure, |
|
801 * <code>false</code> otherwise. |
|
802 * The implementation here always returns <code>false</code>, |
|
803 * but derived classes may override. |
|
804 * |
|
805 * @throws HttpException in case of a problem |
|
806 * @throws IOException in case of an IO problem |
|
807 */ |
|
808 protected boolean createTunnelToTarget(HttpRoute route, |
|
809 HttpContext context) |
|
810 throws HttpException, IOException { |
|
811 |
|
812 HttpHost proxy = route.getProxyHost(); |
|
813 HttpHost target = route.getTargetHost(); |
|
814 HttpResponse response = null; |
|
815 |
|
816 boolean done = false; |
|
817 while (!done) { |
|
818 |
|
819 done = true; |
|
820 |
|
821 if (!this.managedConn.isOpen()) { |
|
822 this.managedConn.open(route, context, this.params); |
|
823 } |
|
824 |
|
825 HttpRequest connect = createConnectRequest(route, context); |
|
826 connect.setParams(this.params); |
|
827 |
|
828 // Populate the execution context |
|
829 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, |
|
830 target); |
|
831 context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, |
|
832 proxy); |
|
833 context.setAttribute(ExecutionContext.HTTP_CONNECTION, |
|
834 managedConn); |
|
835 context.setAttribute(ClientContext.TARGET_AUTH_STATE, |
|
836 targetAuthState); |
|
837 context.setAttribute(ClientContext.PROXY_AUTH_STATE, |
|
838 proxyAuthState); |
|
839 context.setAttribute(ExecutionContext.HTTP_REQUEST, |
|
840 connect); |
|
841 |
|
842 this.requestExec.preProcess(connect, this.httpProcessor, context); |
|
843 |
|
844 response = this.requestExec.execute(connect, this.managedConn, context); |
|
845 |
|
846 response.setParams(this.params); |
|
847 this.requestExec.postProcess(response, this.httpProcessor, context); |
|
848 |
|
849 int status = response.getStatusLine().getStatusCode(); |
|
850 if (status < 200) { |
|
851 throw new HttpException("Unexpected response to CONNECT request: " + |
|
852 response.getStatusLine()); |
|
853 } |
|
854 |
|
855 CredentialsProvider credsProvider = (CredentialsProvider) |
|
856 context.getAttribute(ClientContext.CREDS_PROVIDER); |
|
857 |
|
858 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { |
|
859 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { |
|
860 |
|
861 this.log.debug("Proxy requested authentication"); |
|
862 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( |
|
863 response, context); |
|
864 try { |
|
865 processChallenges( |
|
866 challenges, this.proxyAuthState, this.proxyAuthHandler, |
|
867 response, context); |
|
868 } catch (AuthenticationException ex) { |
|
869 if (this.log.isWarnEnabled()) { |
|
870 this.log.warn("Authentication error: " + ex.getMessage()); |
|
871 break; |
|
872 } |
|
873 } |
|
874 updateAuthState(this.proxyAuthState, proxy, credsProvider); |
|
875 |
|
876 if (this.proxyAuthState.getCredentials() != null) { |
|
877 done = false; |
|
878 |
|
879 // Retry request |
|
880 if (this.reuseStrategy.keepAlive(response, context)) { |
|
881 this.log.debug("Connection kept alive"); |
|
882 // Consume response content |
|
883 HttpEntity entity = response.getEntity(); |
|
884 EntityUtils.consume(entity); |
|
885 } else { |
|
886 this.managedConn.close(); |
|
887 } |
|
888 |
|
889 } |
|
890 |
|
891 } else { |
|
892 // Reset proxy auth scope |
|
893 this.proxyAuthState.setAuthScope(null); |
|
894 } |
|
895 } |
|
896 } |
|
897 |
|
898 int status = response.getStatusLine().getStatusCode(); // can't be null |
|
899 |
|
900 if (status > 299) { |
|
901 |
|
902 // Buffer response content |
|
903 HttpEntity entity = response.getEntity(); |
|
904 if (entity != null) { |
|
905 response.setEntity(new BufferedHttpEntity(entity)); |
|
906 } |
|
907 |
|
908 this.managedConn.close(); |
|
909 throw new TunnelRefusedException("CONNECT refused by proxy: " + |
|
910 response.getStatusLine(), response); |
|
911 } |
|
912 |
|
913 this.managedConn.markReusable(); |
|
914 |
|
915 // How to decide on security of the tunnelled connection? |
|
916 // The socket factory knows only about the segment to the proxy. |
|
917 // Even if that is secure, the hop to the target may be insecure. |
|
918 // Leave it to derived classes, consider insecure by default here. |
|
919 return false; |
|
920 |
|
921 } // createTunnelToTarget |
|
922 |
|
923 |
|
924 |
|
925 /** |
|
926 * Creates a tunnel to an intermediate proxy. |
|
927 * This method is <i>not</i> implemented in this class. |
|
928 * It just throws an exception here. |
|
929 * |
|
930 * @param route the route to establish |
|
931 * @param hop the hop in the route to establish now. |
|
932 * <code>route.getHopTarget(hop)</code> |
|
933 * will return the proxy to tunnel to. |
|
934 * @param context the context for request execution |
|
935 * |
|
936 * @return <code>true</code> if the partially tunnelled connection |
|
937 * is secure, <code>false</code> otherwise. |
|
938 * |
|
939 * @throws HttpException in case of a problem |
|
940 * @throws IOException in case of an IO problem |
|
941 */ |
|
942 protected boolean createTunnelToProxy(HttpRoute route, int hop, |
|
943 HttpContext context) |
|
944 throws HttpException, IOException { |
|
945 |
|
946 // Have a look at createTunnelToTarget and replicate the parts |
|
947 // you need in a custom derived class. If your proxies don't require |
|
948 // authentication, it is not too hard. But for the stock version of |
|
949 // HttpClient, we cannot make such simplifying assumptions and would |
|
950 // have to include proxy authentication code. The HttpComponents team |
|
951 // is currently not in a position to support rarely used code of this |
|
952 // complexity. Feel free to submit patches that refactor the code in |
|
953 // createTunnelToTarget to facilitate re-use for proxy tunnelling. |
|
954 |
|
955 throw new HttpException("Proxy chains are not supported."); |
|
956 } |
|
957 |
|
958 |
|
959 |
|
960 /** |
|
961 * Creates the CONNECT request for tunnelling. |
|
962 * Called by {@link #createTunnelToTarget createTunnelToTarget}. |
|
963 * |
|
964 * @param route the route to establish |
|
965 * @param context the context for request execution |
|
966 * |
|
967 * @return the CONNECT request for tunnelling |
|
968 */ |
|
969 protected HttpRequest createConnectRequest(HttpRoute route, |
|
970 HttpContext context) { |
|
971 // see RFC 2817, section 5.2 and |
|
972 // INTERNET-DRAFT: Tunneling TCP based protocols through |
|
973 // Web proxy servers |
|
974 |
|
975 HttpHost target = route.getTargetHost(); |
|
976 |
|
977 String host = target.getHostName(); |
|
978 int port = target.getPort(); |
|
979 if (port < 0) { |
|
980 Scheme scheme = connManager.getSchemeRegistry(). |
|
981 getScheme(target.getSchemeName()); |
|
982 port = scheme.getDefaultPort(); |
|
983 } |
|
984 |
|
985 StringBuilder buffer = new StringBuilder(host.length() + 6); |
|
986 buffer.append(host); |
|
987 buffer.append(':'); |
|
988 buffer.append(Integer.toString(port)); |
|
989 |
|
990 String authority = buffer.toString(); |
|
991 ProtocolVersion ver = HttpProtocolParams.getVersion(params); |
|
992 HttpRequest req = new BasicHttpRequest |
|
993 ("CONNECT", authority, ver); |
|
994 |
|
995 return req; |
|
996 } |
|
997 |
|
998 |
|
999 /** |
|
1000 * Analyzes a response to check need for a followup. |
|
1001 * |
|
1002 * @param roureq the request and route. |
|
1003 * @param response the response to analayze |
|
1004 * @param context the context used for the current request execution |
|
1005 * |
|
1006 * @return the followup request and route if there is a followup, or |
|
1007 * <code>null</code> if the response should be returned as is |
|
1008 * |
|
1009 * @throws HttpException in case of a problem |
|
1010 * @throws IOException in case of an IO problem |
|
1011 */ |
|
1012 protected RoutedRequest handleResponse(RoutedRequest roureq, |
|
1013 HttpResponse response, |
|
1014 HttpContext context) |
|
1015 throws HttpException, IOException { |
|
1016 |
|
1017 HttpRoute route = roureq.getRoute(); |
|
1018 RequestWrapper request = roureq.getRequest(); |
|
1019 |
|
1020 HttpParams params = request.getParams(); |
|
1021 if (HttpClientParams.isRedirecting(params) && |
|
1022 this.redirectStrategy.isRedirected(request, response, context)) { |
|
1023 |
|
1024 if (redirectCount >= maxRedirects) { |
|
1025 throw new RedirectException("Maximum redirects (" |
|
1026 + maxRedirects + ") exceeded"); |
|
1027 } |
|
1028 redirectCount++; |
|
1029 |
|
1030 // Virtual host cannot be used any longer |
|
1031 virtualHost = null; |
|
1032 |
|
1033 HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context); |
|
1034 HttpRequest orig = request.getOriginal(); |
|
1035 redirect.setHeaders(orig.getAllHeaders()); |
|
1036 |
|
1037 URI uri = redirect.getURI(); |
|
1038 if (uri.getHost() == null) { |
|
1039 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri); |
|
1040 } |
|
1041 |
|
1042 HttpHost newTarget = new HttpHost( |
|
1043 uri.getHost(), |
|
1044 uri.getPort(), |
|
1045 uri.getScheme()); |
|
1046 |
|
1047 // Unset auth scope |
|
1048 targetAuthState.setAuthScope(null); |
|
1049 proxyAuthState.setAuthScope(null); |
|
1050 |
|
1051 // Invalidate auth states if redirecting to another host |
|
1052 if (!route.getTargetHost().equals(newTarget)) { |
|
1053 targetAuthState.invalidate(); |
|
1054 AuthScheme authScheme = proxyAuthState.getAuthScheme(); |
|
1055 if (authScheme != null && authScheme.isConnectionBased()) { |
|
1056 proxyAuthState.invalidate(); |
|
1057 } |
|
1058 } |
|
1059 |
|
1060 RequestWrapper wrapper = wrapRequest(redirect); |
|
1061 wrapper.setParams(params); |
|
1062 |
|
1063 HttpRoute newRoute = determineRoute(newTarget, wrapper, context); |
|
1064 RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); |
|
1065 |
|
1066 if (this.log.isDebugEnabled()) { |
|
1067 this.log.debug("Redirecting to '" + uri + "' via " + newRoute); |
|
1068 } |
|
1069 |
|
1070 return newRequest; |
|
1071 } |
|
1072 |
|
1073 CredentialsProvider credsProvider = (CredentialsProvider) |
|
1074 context.getAttribute(ClientContext.CREDS_PROVIDER); |
|
1075 |
|
1076 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { |
|
1077 |
|
1078 if (this.targetAuthHandler.isAuthenticationRequested(response, context)) { |
|
1079 |
|
1080 HttpHost target = (HttpHost) |
|
1081 context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); |
|
1082 if (target == null) { |
|
1083 target = route.getTargetHost(); |
|
1084 } |
|
1085 |
|
1086 this.log.debug("Target requested authentication"); |
|
1087 Map<String, Header> challenges = this.targetAuthHandler.getChallenges( |
|
1088 response, context); |
|
1089 try { |
|
1090 processChallenges(challenges, |
|
1091 this.targetAuthState, this.targetAuthHandler, |
|
1092 response, context); |
|
1093 } catch (AuthenticationException ex) { |
|
1094 if (this.log.isWarnEnabled()) { |
|
1095 this.log.warn("Authentication error: " + ex.getMessage()); |
|
1096 return null; |
|
1097 } |
|
1098 } |
|
1099 updateAuthState(this.targetAuthState, target, credsProvider); |
|
1100 |
|
1101 if (this.targetAuthState.getCredentials() != null) { |
|
1102 // Re-try the same request via the same route |
|
1103 return roureq; |
|
1104 } else { |
|
1105 return null; |
|
1106 } |
|
1107 } else { |
|
1108 // Reset target auth scope |
|
1109 this.targetAuthState.setAuthScope(null); |
|
1110 } |
|
1111 |
|
1112 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { |
|
1113 |
|
1114 HttpHost proxy = route.getProxyHost(); |
|
1115 |
|
1116 this.log.debug("Proxy requested authentication"); |
|
1117 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( |
|
1118 response, context); |
|
1119 try { |
|
1120 processChallenges(challenges, |
|
1121 this.proxyAuthState, this.proxyAuthHandler, |
|
1122 response, context); |
|
1123 } catch (AuthenticationException ex) { |
|
1124 if (this.log.isWarnEnabled()) { |
|
1125 this.log.warn("Authentication error: " + ex.getMessage()); |
|
1126 return null; |
|
1127 } |
|
1128 } |
|
1129 updateAuthState(this.proxyAuthState, proxy, credsProvider); |
|
1130 |
|
1131 if (this.proxyAuthState.getCredentials() != null) { |
|
1132 // Re-try the same request via the same route |
|
1133 return roureq; |
|
1134 } else { |
|
1135 return null; |
|
1136 } |
|
1137 } else { |
|
1138 // Reset proxy auth scope |
|
1139 this.proxyAuthState.setAuthScope(null); |
|
1140 } |
|
1141 } |
|
1142 return null; |
|
1143 } // handleResponse |
|
1144 |
|
1145 |
|
1146 /** |
|
1147 * Shuts down the connection. |
|
1148 * This method is called from a <code>catch</code> block in |
|
1149 * {@link #execute execute} during exception handling. |
|
1150 */ |
|
1151 private void abortConnection() { |
|
1152 ManagedClientConnection mcc = managedConn; |
|
1153 if (mcc != null) { |
|
1154 // we got here as the result of an exception |
|
1155 // no response will be returned, release the connection |
|
1156 managedConn = null; |
|
1157 try { |
|
1158 mcc.abortConnection(); |
|
1159 } catch (IOException ex) { |
|
1160 if (this.log.isDebugEnabled()) { |
|
1161 this.log.debug(ex.getMessage(), ex); |
|
1162 } |
|
1163 } |
|
1164 // ensure the connection manager properly releases this connection |
|
1165 try { |
|
1166 mcc.releaseConnection(); |
|
1167 } catch(IOException ignored) { |
|
1168 this.log.debug("Error releasing connection", ignored); |
|
1169 } |
|
1170 } |
|
1171 } // abortConnection |
|
1172 |
|
1173 |
|
1174 private void processChallenges( |
|
1175 final Map<String, Header> challenges, |
|
1176 final AuthState authState, |
|
1177 final AuthenticationHandler authHandler, |
|
1178 final HttpResponse response, |
|
1179 final HttpContext context) |
|
1180 throws MalformedChallengeException, AuthenticationException { |
|
1181 |
|
1182 AuthScheme authScheme = authState.getAuthScheme(); |
|
1183 if (authScheme == null) { |
|
1184 // Authentication not attempted before |
|
1185 authScheme = authHandler.selectScheme(challenges, response, context); |
|
1186 authState.setAuthScheme(authScheme); |
|
1187 } |
|
1188 String id = authScheme.getSchemeName(); |
|
1189 |
|
1190 Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); |
|
1191 if (challenge == null) { |
|
1192 throw new AuthenticationException(id + |
|
1193 " authorization challenge expected, but not found"); |
|
1194 } |
|
1195 authScheme.processChallenge(challenge); |
|
1196 this.log.debug("Authorization challenge processed"); |
|
1197 } |
|
1198 |
|
1199 |
|
1200 private void updateAuthState( |
|
1201 final AuthState authState, |
|
1202 final HttpHost host, |
|
1203 final CredentialsProvider credsProvider) { |
|
1204 |
|
1205 if (!authState.isValid()) { |
|
1206 return; |
|
1207 } |
|
1208 |
|
1209 String hostname = host.getHostName(); |
|
1210 int port = host.getPort(); |
|
1211 if (port < 0) { |
|
1212 Scheme scheme = connManager.getSchemeRegistry().getScheme(host); |
|
1213 port = scheme.getDefaultPort(); |
|
1214 } |
|
1215 |
|
1216 AuthScheme authScheme = authState.getAuthScheme(); |
|
1217 AuthScope authScope = new AuthScope( |
|
1218 hostname, |
|
1219 port, |
|
1220 authScheme.getRealm(), |
|
1221 authScheme.getSchemeName()); |
|
1222 |
|
1223 if (this.log.isDebugEnabled()) { |
|
1224 this.log.debug("Authentication scope: " + authScope); |
|
1225 } |
|
1226 Credentials creds = authState.getCredentials(); |
|
1227 if (creds == null) { |
|
1228 creds = credsProvider.getCredentials(authScope); |
|
1229 if (this.log.isDebugEnabled()) { |
|
1230 if (creds != null) { |
|
1231 this.log.debug("Found credentials"); |
|
1232 } else { |
|
1233 this.log.debug("Credentials not found"); |
|
1234 } |
|
1235 } |
|
1236 } else { |
|
1237 if (authScheme.isComplete()) { |
|
1238 this.log.debug("Authentication failed"); |
|
1239 creds = null; |
|
1240 } |
|
1241 } |
|
1242 authState.setAuthScope(authScope); |
|
1243 authState.setCredentials(creds); |
|
1244 } |
|
1245 |
|
1246 private void invalidateAuthIfSuccessful(final AuthState authState) { |
|
1247 AuthScheme authscheme = authState.getAuthScheme(); |
|
1248 if (authscheme != null |
|
1249 && authscheme.isConnectionBased() |
|
1250 && authscheme.isComplete() |
|
1251 && authState.getCredentials() != null) { |
|
1252 authState.invalidate(); |
|
1253 } |
|
1254 } |
|
1255 |
|
1256 } // class DefaultClientRequestDirector |