1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1256 @@ 1.4 +/* 1.5 + * ==================================================================== 1.6 + * Licensed to the Apache Software Foundation (ASF) under one 1.7 + * or more contributor license agreements. See the NOTICE file 1.8 + * distributed with this work for additional information 1.9 + * regarding copyright ownership. The ASF licenses this file 1.10 + * to you under the Apache License, Version 2.0 (the 1.11 + * "License"); you may not use this file except in compliance 1.12 + * with the License. You may obtain a copy of the License at 1.13 + * 1.14 + * http://www.apache.org/licenses/LICENSE-2.0 1.15 + * 1.16 + * Unless required by applicable law or agreed to in writing, 1.17 + * software distributed under the License is distributed on an 1.18 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 1.19 + * KIND, either express or implied. See the License for the 1.20 + * specific language governing permissions and limitations 1.21 + * under the License. 1.22 + * ==================================================================== 1.23 + * 1.24 + * This software consists of voluntary contributions made by many 1.25 + * individuals on behalf of the Apache Software Foundation. For more 1.26 + * information on the Apache Software Foundation, please see 1.27 + * <http://www.apache.org/>. 1.28 + * 1.29 + */ 1.30 + 1.31 +package ch.boye.httpclientandroidlib.impl.client; 1.32 + 1.33 +import java.io.IOException; 1.34 +import java.io.InterruptedIOException; 1.35 +import java.net.URI; 1.36 +import java.net.URISyntaxException; 1.37 +import java.util.Locale; 1.38 +import java.util.Map; 1.39 +import java.util.concurrent.TimeUnit; 1.40 + 1.41 +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; 1.42 + 1.43 +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; 1.44 +/* LogFactory removed by HttpClient for Android script. */ 1.45 +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; 1.46 +import ch.boye.httpclientandroidlib.Header; 1.47 +import ch.boye.httpclientandroidlib.HttpEntity; 1.48 +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; 1.49 +import ch.boye.httpclientandroidlib.HttpException; 1.50 +import ch.boye.httpclientandroidlib.HttpHost; 1.51 +import ch.boye.httpclientandroidlib.HttpRequest; 1.52 +import ch.boye.httpclientandroidlib.HttpResponse; 1.53 +import ch.boye.httpclientandroidlib.ProtocolException; 1.54 +import ch.boye.httpclientandroidlib.ProtocolVersion; 1.55 +import ch.boye.httpclientandroidlib.auth.AuthScheme; 1.56 +import ch.boye.httpclientandroidlib.auth.AuthScope; 1.57 +import ch.boye.httpclientandroidlib.auth.AuthState; 1.58 +import ch.boye.httpclientandroidlib.auth.AuthenticationException; 1.59 +import ch.boye.httpclientandroidlib.auth.Credentials; 1.60 +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; 1.61 +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; 1.62 +import ch.boye.httpclientandroidlib.client.RedirectStrategy; 1.63 +import ch.boye.httpclientandroidlib.client.RequestDirector; 1.64 +import ch.boye.httpclientandroidlib.client.CredentialsProvider; 1.65 +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; 1.66 +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; 1.67 +import ch.boye.httpclientandroidlib.client.RedirectException; 1.68 +import ch.boye.httpclientandroidlib.client.UserTokenHandler; 1.69 +import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest; 1.70 +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; 1.71 +import ch.boye.httpclientandroidlib.client.params.ClientPNames; 1.72 +import ch.boye.httpclientandroidlib.client.params.HttpClientParams; 1.73 +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; 1.74 +import ch.boye.httpclientandroidlib.client.utils.URIUtils; 1.75 +import ch.boye.httpclientandroidlib.conn.BasicManagedEntity; 1.76 +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; 1.77 +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; 1.78 +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; 1.79 +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; 1.80 +import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams; 1.81 +import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector; 1.82 +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; 1.83 +import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector; 1.84 +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; 1.85 +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; 1.86 +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; 1.87 +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; 1.88 +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; 1.89 +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; 1.90 +import ch.boye.httpclientandroidlib.params.HttpParams; 1.91 +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; 1.92 +import ch.boye.httpclientandroidlib.protocol.ExecutionContext; 1.93 +import ch.boye.httpclientandroidlib.protocol.HttpContext; 1.94 +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; 1.95 +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; 1.96 +import ch.boye.httpclientandroidlib.util.EntityUtils; 1.97 + 1.98 +/** 1.99 + * Default implementation of {@link RequestDirector}. 1.100 + * <p> 1.101 + * The following parameters can be used to customize the behavior of this 1.102 + * class: 1.103 + * <ul> 1.104 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> 1.105 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> 1.106 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> 1.107 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> 1.108 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> 1.109 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> 1.110 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> 1.111 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> 1.112 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> 1.113 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> 1.114 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> 1.115 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> 1.116 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> 1.117 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> 1.118 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> 1.119 + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> 1.120 + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> 1.121 + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> 1.122 + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> 1.123 + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> 1.124 + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> 1.125 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> 1.126 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> 1.127 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> 1.128 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> 1.129 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> 1.130 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> 1.131 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> 1.132 + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> 1.133 + * </ul> 1.134 + * 1.135 + * @since 4.0 1.136 + */ 1.137 +@SuppressWarnings("deprecation") 1.138 +@NotThreadSafe // e.g. managedConn 1.139 +public class DefaultRequestDirector implements RequestDirector { 1.140 + 1.141 + public HttpClientAndroidLog log; 1.142 + 1.143 + /** The connection manager. */ 1.144 + protected final ClientConnectionManager connManager; 1.145 + 1.146 + /** The route planner. */ 1.147 + protected final HttpRoutePlanner routePlanner; 1.148 + 1.149 + /** The connection re-use strategy. */ 1.150 + protected final ConnectionReuseStrategy reuseStrategy; 1.151 + 1.152 + /** The keep-alive duration strategy. */ 1.153 + protected final ConnectionKeepAliveStrategy keepAliveStrategy; 1.154 + 1.155 + /** The request executor. */ 1.156 + protected final HttpRequestExecutor requestExec; 1.157 + 1.158 + /** The HTTP protocol processor. */ 1.159 + protected final HttpProcessor httpProcessor; 1.160 + 1.161 + /** The request retry handler. */ 1.162 + protected final HttpRequestRetryHandler retryHandler; 1.163 + 1.164 + /** The redirect handler. */ 1.165 + @Deprecated 1.166 + protected final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler = null; 1.167 + 1.168 + /** The redirect strategy. */ 1.169 + protected final RedirectStrategy redirectStrategy; 1.170 + 1.171 + /** The target authentication handler. */ 1.172 + protected final AuthenticationHandler targetAuthHandler; 1.173 + 1.174 + /** The proxy authentication handler. */ 1.175 + protected final AuthenticationHandler proxyAuthHandler; 1.176 + 1.177 + /** The user token handler. */ 1.178 + protected final UserTokenHandler userTokenHandler; 1.179 + 1.180 + /** The HTTP parameters. */ 1.181 + protected final HttpParams params; 1.182 + 1.183 + /** The currently allocated connection. */ 1.184 + protected ManagedClientConnection managedConn; 1.185 + 1.186 + protected final AuthState targetAuthState; 1.187 + 1.188 + protected final AuthState proxyAuthState; 1.189 + 1.190 + private int execCount; 1.191 + 1.192 + private int redirectCount; 1.193 + 1.194 + private int maxRedirects; 1.195 + 1.196 + private HttpHost virtualHost; 1.197 + 1.198 + @Deprecated 1.199 + public DefaultRequestDirector( 1.200 + final HttpRequestExecutor requestExec, 1.201 + final ClientConnectionManager conman, 1.202 + final ConnectionReuseStrategy reustrat, 1.203 + final ConnectionKeepAliveStrategy kastrat, 1.204 + final HttpRoutePlanner rouplan, 1.205 + final HttpProcessor httpProcessor, 1.206 + final HttpRequestRetryHandler retryHandler, 1.207 + final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler, 1.208 + final AuthenticationHandler targetAuthHandler, 1.209 + final AuthenticationHandler proxyAuthHandler, 1.210 + final UserTokenHandler userTokenHandler, 1.211 + final HttpParams params) { 1.212 + this(new HttpClientAndroidLog(DefaultRequestDirector.class), 1.213 + requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, 1.214 + new DefaultRedirectStrategyAdaptor(redirectHandler), 1.215 + targetAuthHandler, proxyAuthHandler, userTokenHandler, params); 1.216 + } 1.217 + 1.218 + 1.219 + /** 1.220 + * @since 4.1 1.221 + */ 1.222 + public DefaultRequestDirector( 1.223 + final HttpClientAndroidLog log, 1.224 + final HttpRequestExecutor requestExec, 1.225 + final ClientConnectionManager conman, 1.226 + final ConnectionReuseStrategy reustrat, 1.227 + final ConnectionKeepAliveStrategy kastrat, 1.228 + final HttpRoutePlanner rouplan, 1.229 + final HttpProcessor httpProcessor, 1.230 + final HttpRequestRetryHandler retryHandler, 1.231 + final RedirectStrategy redirectStrategy, 1.232 + final AuthenticationHandler targetAuthHandler, 1.233 + final AuthenticationHandler proxyAuthHandler, 1.234 + final UserTokenHandler userTokenHandler, 1.235 + final HttpParams params) { 1.236 + 1.237 + if (log == null) { 1.238 + throw new IllegalArgumentException 1.239 + ("Log may not be null."); 1.240 + } 1.241 + if (requestExec == null) { 1.242 + throw new IllegalArgumentException 1.243 + ("Request executor may not be null."); 1.244 + } 1.245 + if (conman == null) { 1.246 + throw new IllegalArgumentException 1.247 + ("Client connection manager may not be null."); 1.248 + } 1.249 + if (reustrat == null) { 1.250 + throw new IllegalArgumentException 1.251 + ("Connection reuse strategy may not be null."); 1.252 + } 1.253 + if (kastrat == null) { 1.254 + throw new IllegalArgumentException 1.255 + ("Connection keep alive strategy may not be null."); 1.256 + } 1.257 + if (rouplan == null) { 1.258 + throw new IllegalArgumentException 1.259 + ("Route planner may not be null."); 1.260 + } 1.261 + if (httpProcessor == null) { 1.262 + throw new IllegalArgumentException 1.263 + ("HTTP protocol processor may not be null."); 1.264 + } 1.265 + if (retryHandler == null) { 1.266 + throw new IllegalArgumentException 1.267 + ("HTTP request retry handler may not be null."); 1.268 + } 1.269 + if (redirectStrategy == null) { 1.270 + throw new IllegalArgumentException 1.271 + ("Redirect strategy may not be null."); 1.272 + } 1.273 + if (targetAuthHandler == null) { 1.274 + throw new IllegalArgumentException 1.275 + ("Target authentication handler may not be null."); 1.276 + } 1.277 + if (proxyAuthHandler == null) { 1.278 + throw new IllegalArgumentException 1.279 + ("Proxy authentication handler may not be null."); 1.280 + } 1.281 + if (userTokenHandler == null) { 1.282 + throw new IllegalArgumentException 1.283 + ("User token handler may not be null."); 1.284 + } 1.285 + if (params == null) { 1.286 + throw new IllegalArgumentException 1.287 + ("HTTP parameters may not be null"); 1.288 + } 1.289 + this.log = log; 1.290 + this.requestExec = requestExec; 1.291 + this.connManager = conman; 1.292 + this.reuseStrategy = reustrat; 1.293 + this.keepAliveStrategy = kastrat; 1.294 + this.routePlanner = rouplan; 1.295 + this.httpProcessor = httpProcessor; 1.296 + this.retryHandler = retryHandler; 1.297 + this.redirectStrategy = redirectStrategy; 1.298 + this.targetAuthHandler = targetAuthHandler; 1.299 + this.proxyAuthHandler = proxyAuthHandler; 1.300 + this.userTokenHandler = userTokenHandler; 1.301 + this.params = params; 1.302 + 1.303 + this.managedConn = null; 1.304 + 1.305 + this.execCount = 0; 1.306 + this.redirectCount = 0; 1.307 + this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); 1.308 + this.targetAuthState = new AuthState(); 1.309 + this.proxyAuthState = new AuthState(); 1.310 + } // constructor 1.311 + 1.312 + 1.313 + private RequestWrapper wrapRequest( 1.314 + final HttpRequest request) throws ProtocolException { 1.315 + if (request instanceof HttpEntityEnclosingRequest) { 1.316 + return new EntityEnclosingRequestWrapper( 1.317 + (HttpEntityEnclosingRequest) request); 1.318 + } else { 1.319 + return new RequestWrapper( 1.320 + request); 1.321 + } 1.322 + } 1.323 + 1.324 + 1.325 + protected void rewriteRequestURI( 1.326 + final RequestWrapper request, 1.327 + final HttpRoute route) throws ProtocolException { 1.328 + try { 1.329 + 1.330 + URI uri = request.getURI(); 1.331 + if (route.getProxyHost() != null && !route.isTunnelled()) { 1.332 + // Make sure the request URI is absolute 1.333 + if (!uri.isAbsolute()) { 1.334 + HttpHost target = route.getTargetHost(); 1.335 + uri = URIUtils.rewriteURI(uri, target); 1.336 + request.setURI(uri); 1.337 + } 1.338 + } else { 1.339 + // Make sure the request URI is relative 1.340 + if (uri.isAbsolute()) { 1.341 + uri = URIUtils.rewriteURI(uri, null); 1.342 + request.setURI(uri); 1.343 + } 1.344 + } 1.345 + 1.346 + } catch (URISyntaxException ex) { 1.347 + throw new ProtocolException("Invalid URI: " + 1.348 + request.getRequestLine().getUri(), ex); 1.349 + } 1.350 + } 1.351 + 1.352 + 1.353 + // non-javadoc, see interface ClientRequestDirector 1.354 + public HttpResponse execute(HttpHost target, HttpRequest request, 1.355 + HttpContext context) 1.356 + throws HttpException, IOException { 1.357 + 1.358 + HttpRequest orig = request; 1.359 + RequestWrapper origWrapper = wrapRequest(orig); 1.360 + origWrapper.setParams(params); 1.361 + HttpRoute origRoute = determineRoute(target, origWrapper, context); 1.362 + 1.363 + virtualHost = (HttpHost) orig.getParams().getParameter( 1.364 + ClientPNames.VIRTUAL_HOST); 1.365 + 1.366 + // HTTPCLIENT-1092 - add the port if necessary 1.367 + if (virtualHost != null && virtualHost.getPort() == -1) 1.368 + { 1.369 + int port = target.getPort(); 1.370 + if (port != -1){ 1.371 + virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName()); 1.372 + } 1.373 + } 1.374 + 1.375 + RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); 1.376 + 1.377 + boolean reuse = false; 1.378 + boolean done = false; 1.379 + try { 1.380 + HttpResponse response = null; 1.381 + while (!done) { 1.382 + // In this loop, the RoutedRequest may be replaced by a 1.383 + // followup request and route. The request and route passed 1.384 + // in the method arguments will be replaced. The original 1.385 + // request is still available in 'orig'. 1.386 + 1.387 + RequestWrapper wrapper = roureq.getRequest(); 1.388 + HttpRoute route = roureq.getRoute(); 1.389 + response = null; 1.390 + 1.391 + // See if we have a user token bound to the execution context 1.392 + Object userToken = context.getAttribute(ClientContext.USER_TOKEN); 1.393 + 1.394 + // Allocate connection if needed 1.395 + if (managedConn == null) { 1.396 + ClientConnectionRequest connRequest = connManager.requestConnection( 1.397 + route, userToken); 1.398 + if (orig instanceof AbortableHttpRequest) { 1.399 + ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); 1.400 + } 1.401 + 1.402 + long timeout = ConnManagerParams.getTimeout(params); 1.403 + try { 1.404 + managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); 1.405 + } catch(InterruptedException interrupted) { 1.406 + InterruptedIOException iox = new InterruptedIOException(); 1.407 + iox.initCause(interrupted); 1.408 + throw iox; 1.409 + } 1.410 + 1.411 + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { 1.412 + // validate connection 1.413 + if (managedConn.isOpen()) { 1.414 + this.log.debug("Stale connection check"); 1.415 + if (managedConn.isStale()) { 1.416 + this.log.debug("Stale connection detected"); 1.417 + managedConn.close(); 1.418 + } 1.419 + } 1.420 + } 1.421 + } 1.422 + 1.423 + if (orig instanceof AbortableHttpRequest) { 1.424 + ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); 1.425 + } 1.426 + 1.427 + try { 1.428 + tryConnect(roureq, context); 1.429 + } catch (TunnelRefusedException ex) { 1.430 + if (this.log.isDebugEnabled()) { 1.431 + this.log.debug(ex.getMessage()); 1.432 + } 1.433 + response = ex.getResponse(); 1.434 + break; 1.435 + } 1.436 + 1.437 + // Reset headers on the request wrapper 1.438 + wrapper.resetHeaders(); 1.439 + 1.440 + // Re-write request URI if needed 1.441 + rewriteRequestURI(wrapper, route); 1.442 + 1.443 + // Use virtual host if set 1.444 + target = virtualHost; 1.445 + 1.446 + if (target == null) { 1.447 + target = route.getTargetHost(); 1.448 + } 1.449 + 1.450 + HttpHost proxy = route.getProxyHost(); 1.451 + 1.452 + // Populate the execution context 1.453 + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, 1.454 + target); 1.455 + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, 1.456 + proxy); 1.457 + context.setAttribute(ExecutionContext.HTTP_CONNECTION, 1.458 + managedConn); 1.459 + context.setAttribute(ClientContext.TARGET_AUTH_STATE, 1.460 + targetAuthState); 1.461 + context.setAttribute(ClientContext.PROXY_AUTH_STATE, 1.462 + proxyAuthState); 1.463 + 1.464 + // Run request protocol interceptors 1.465 + requestExec.preProcess(wrapper, httpProcessor, context); 1.466 + 1.467 + response = tryExecute(roureq, context); 1.468 + if (response == null) { 1.469 + // Need to start over 1.470 + continue; 1.471 + } 1.472 + 1.473 + // Run response protocol interceptors 1.474 + response.setParams(params); 1.475 + requestExec.postProcess(response, httpProcessor, context); 1.476 + 1.477 + 1.478 + // The connection is in or can be brought to a re-usable state. 1.479 + reuse = reuseStrategy.keepAlive(response, context); 1.480 + if (reuse) { 1.481 + // Set the idle duration of this connection 1.482 + long duration = keepAliveStrategy.getKeepAliveDuration(response, context); 1.483 + if (this.log.isDebugEnabled()) { 1.484 + String s; 1.485 + if (duration > 0) { 1.486 + s = "for " + duration + " " + TimeUnit.MILLISECONDS; 1.487 + } else { 1.488 + s = "indefinitely"; 1.489 + } 1.490 + this.log.debug("Connection can be kept alive " + s); 1.491 + } 1.492 + managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); 1.493 + } 1.494 + 1.495 + RoutedRequest followup = handleResponse(roureq, response, context); 1.496 + if (followup == null) { 1.497 + done = true; 1.498 + } else { 1.499 + if (reuse) { 1.500 + // Make sure the response body is fully consumed, if present 1.501 + HttpEntity entity = response.getEntity(); 1.502 + EntityUtils.consume(entity); 1.503 + // entity consumed above is not an auto-release entity, 1.504 + // need to mark the connection re-usable explicitly 1.505 + managedConn.markReusable(); 1.506 + } else { 1.507 + managedConn.close(); 1.508 + invalidateAuthIfSuccessful(this.proxyAuthState); 1.509 + invalidateAuthIfSuccessful(this.targetAuthState); 1.510 + } 1.511 + // check if we can use the same connection for the followup 1.512 + if (!followup.getRoute().equals(roureq.getRoute())) { 1.513 + releaseConnection(); 1.514 + } 1.515 + roureq = followup; 1.516 + } 1.517 + 1.518 + if (managedConn != null && userToken == null) { 1.519 + userToken = userTokenHandler.getUserToken(context); 1.520 + context.setAttribute(ClientContext.USER_TOKEN, userToken); 1.521 + if (userToken != null) { 1.522 + managedConn.setState(userToken); 1.523 + } 1.524 + } 1.525 + 1.526 + } // while not done 1.527 + 1.528 + 1.529 + // check for entity, release connection if possible 1.530 + if ((response == null) || (response.getEntity() == null) || 1.531 + !response.getEntity().isStreaming()) { 1.532 + // connection not needed and (assumed to be) in re-usable state 1.533 + if (reuse) 1.534 + managedConn.markReusable(); 1.535 + releaseConnection(); 1.536 + } else { 1.537 + // install an auto-release entity 1.538 + HttpEntity entity = response.getEntity(); 1.539 + entity = new BasicManagedEntity(entity, managedConn, reuse); 1.540 + response.setEntity(entity); 1.541 + } 1.542 + 1.543 + return response; 1.544 + 1.545 + } catch (ConnectionShutdownException ex) { 1.546 + InterruptedIOException ioex = new InterruptedIOException( 1.547 + "Connection has been shut down"); 1.548 + ioex.initCause(ex); 1.549 + throw ioex; 1.550 + } catch (HttpException ex) { 1.551 + abortConnection(); 1.552 + throw ex; 1.553 + } catch (IOException ex) { 1.554 + abortConnection(); 1.555 + throw ex; 1.556 + } catch (RuntimeException ex) { 1.557 + abortConnection(); 1.558 + throw ex; 1.559 + } 1.560 + } // execute 1.561 + 1.562 + /** 1.563 + * Establish connection either directly or through a tunnel and retry in case of 1.564 + * a recoverable I/O failure 1.565 + */ 1.566 + private void tryConnect( 1.567 + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { 1.568 + HttpRoute route = req.getRoute(); 1.569 + 1.570 + int connectCount = 0; 1.571 + for (;;) { 1.572 + // Increment connect count 1.573 + connectCount++; 1.574 + try { 1.575 + if (!managedConn.isOpen()) { 1.576 + managedConn.open(route, context, params); 1.577 + } else { 1.578 + managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params)); 1.579 + } 1.580 + establishRoute(route, context); 1.581 + break; 1.582 + } catch (IOException ex) { 1.583 + try { 1.584 + managedConn.close(); 1.585 + } catch (IOException ignore) { 1.586 + } 1.587 + if (retryHandler.retryRequest(ex, connectCount, context)) { 1.588 + if (this.log.isInfoEnabled()) { 1.589 + this.log.info("I/O exception ("+ ex.getClass().getName() + 1.590 + ") caught when connecting to the target host: " 1.591 + + ex.getMessage()); 1.592 + } 1.593 + if (this.log.isDebugEnabled()) { 1.594 + this.log.debug(ex.getMessage(), ex); 1.595 + } 1.596 + this.log.info("Retrying connect"); 1.597 + } else { 1.598 + throw ex; 1.599 + } 1.600 + } 1.601 + } 1.602 + } 1.603 + 1.604 + /** 1.605 + * Execute request and retry in case of a recoverable I/O failure 1.606 + */ 1.607 + private HttpResponse tryExecute( 1.608 + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { 1.609 + RequestWrapper wrapper = req.getRequest(); 1.610 + HttpRoute route = req.getRoute(); 1.611 + HttpResponse response = null; 1.612 + 1.613 + Exception retryReason = null; 1.614 + for (;;) { 1.615 + // Increment total exec count (with redirects) 1.616 + execCount++; 1.617 + // Increment exec count for this particular request 1.618 + wrapper.incrementExecCount(); 1.619 + if (!wrapper.isRepeatable()) { 1.620 + this.log.debug("Cannot retry non-repeatable request"); 1.621 + if (retryReason != null) { 1.622 + throw new NonRepeatableRequestException("Cannot retry request " + 1.623 + "with a non-repeatable request entity. The cause lists the " + 1.624 + "reason the original request failed.", retryReason); 1.625 + } else { 1.626 + throw new NonRepeatableRequestException("Cannot retry request " + 1.627 + "with a non-repeatable request entity."); 1.628 + } 1.629 + } 1.630 + 1.631 + try { 1.632 + if (!managedConn.isOpen()) { 1.633 + // If we have a direct route to the target host 1.634 + // just re-open connection and re-try the request 1.635 + if (!route.isTunnelled()) { 1.636 + this.log.debug("Reopening the direct connection."); 1.637 + managedConn.open(route, context, params); 1.638 + } else { 1.639 + // otherwise give up 1.640 + this.log.debug("Proxied connection. Need to start over."); 1.641 + break; 1.642 + } 1.643 + } 1.644 + 1.645 + if (this.log.isDebugEnabled()) { 1.646 + this.log.debug("Attempt " + execCount + " to execute request"); 1.647 + } 1.648 + response = requestExec.execute(wrapper, managedConn, context); 1.649 + break; 1.650 + 1.651 + } catch (IOException ex) { 1.652 + this.log.debug("Closing the connection."); 1.653 + try { 1.654 + managedConn.close(); 1.655 + } catch (IOException ignore) { 1.656 + } 1.657 + if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) { 1.658 + if (this.log.isInfoEnabled()) { 1.659 + this.log.info("I/O exception ("+ ex.getClass().getName() + 1.660 + ") caught when processing request: " 1.661 + + ex.getMessage()); 1.662 + } 1.663 + if (this.log.isDebugEnabled()) { 1.664 + this.log.debug(ex.getMessage(), ex); 1.665 + } 1.666 + this.log.info("Retrying request"); 1.667 + retryReason = ex; 1.668 + } else { 1.669 + throw ex; 1.670 + } 1.671 + } 1.672 + } 1.673 + return response; 1.674 + } 1.675 + 1.676 + /** 1.677 + * Returns the connection back to the connection manager 1.678 + * and prepares for retrieving a new connection during 1.679 + * the next request. 1.680 + */ 1.681 + protected void releaseConnection() { 1.682 + // Release the connection through the ManagedConnection instead of the 1.683 + // ConnectionManager directly. This lets the connection control how 1.684 + // it is released. 1.685 + try { 1.686 + managedConn.releaseConnection(); 1.687 + } catch(IOException ignored) { 1.688 + this.log.debug("IOException releasing connection", ignored); 1.689 + } 1.690 + managedConn = null; 1.691 + } 1.692 + 1.693 + /** 1.694 + * Determines the route for a request. 1.695 + * Called by {@link #execute} 1.696 + * to determine the route for either the original or a followup request. 1.697 + * 1.698 + * @param target the target host for the request. 1.699 + * Implementations may accept <code>null</code> 1.700 + * if they can still determine a route, for example 1.701 + * to a default target or by inspecting the request. 1.702 + * @param request the request to execute 1.703 + * @param context the context to use for the execution, 1.704 + * never <code>null</code> 1.705 + * 1.706 + * @return the route the request should take 1.707 + * 1.708 + * @throws HttpException in case of a problem 1.709 + */ 1.710 + protected HttpRoute determineRoute(HttpHost target, 1.711 + HttpRequest request, 1.712 + HttpContext context) 1.713 + throws HttpException { 1.714 + 1.715 + if (target == null) { 1.716 + target = (HttpHost) request.getParams().getParameter( 1.717 + ClientPNames.DEFAULT_HOST); 1.718 + } 1.719 + if (target == null) { 1.720 + throw new IllegalStateException 1.721 + ("Target host must not be null, or set in parameters."); 1.722 + } 1.723 + 1.724 + return this.routePlanner.determineRoute(target, request, context); 1.725 + } 1.726 + 1.727 + 1.728 + /** 1.729 + * Establishes the target route. 1.730 + * 1.731 + * @param route the route to establish 1.732 + * @param context the context for the request execution 1.733 + * 1.734 + * @throws HttpException in case of a problem 1.735 + * @throws IOException in case of an IO problem 1.736 + */ 1.737 + protected void establishRoute(HttpRoute route, HttpContext context) 1.738 + throws HttpException, IOException { 1.739 + 1.740 + HttpRouteDirector rowdy = new BasicRouteDirector(); 1.741 + int step; 1.742 + do { 1.743 + HttpRoute fact = managedConn.getRoute(); 1.744 + step = rowdy.nextStep(route, fact); 1.745 + 1.746 + switch (step) { 1.747 + 1.748 + case HttpRouteDirector.CONNECT_TARGET: 1.749 + case HttpRouteDirector.CONNECT_PROXY: 1.750 + managedConn.open(route, context, this.params); 1.751 + break; 1.752 + 1.753 + case HttpRouteDirector.TUNNEL_TARGET: { 1.754 + boolean secure = createTunnelToTarget(route, context); 1.755 + this.log.debug("Tunnel to target created."); 1.756 + managedConn.tunnelTarget(secure, this.params); 1.757 + } break; 1.758 + 1.759 + case HttpRouteDirector.TUNNEL_PROXY: { 1.760 + // The most simple example for this case is a proxy chain 1.761 + // of two proxies, where P1 must be tunnelled to P2. 1.762 + // route: Source -> P1 -> P2 -> Target (3 hops) 1.763 + // fact: Source -> P1 -> Target (2 hops) 1.764 + final int hop = fact.getHopCount()-1; // the hop to establish 1.765 + boolean secure = createTunnelToProxy(route, hop, context); 1.766 + this.log.debug("Tunnel to proxy created."); 1.767 + managedConn.tunnelProxy(route.getHopTarget(hop), 1.768 + secure, this.params); 1.769 + } break; 1.770 + 1.771 + 1.772 + case HttpRouteDirector.LAYER_PROTOCOL: 1.773 + managedConn.layerProtocol(context, this.params); 1.774 + break; 1.775 + 1.776 + case HttpRouteDirector.UNREACHABLE: 1.777 + throw new HttpException("Unable to establish route: " + 1.778 + "planned = " + route + "; current = " + fact); 1.779 + case HttpRouteDirector.COMPLETE: 1.780 + // do nothing 1.781 + break; 1.782 + default: 1.783 + throw new IllegalStateException("Unknown step indicator " 1.784 + + step + " from RouteDirector."); 1.785 + } 1.786 + 1.787 + } while (step > HttpRouteDirector.COMPLETE); 1.788 + 1.789 + } // establishConnection 1.790 + 1.791 + 1.792 + /** 1.793 + * Creates a tunnel to the target server. 1.794 + * The connection must be established to the (last) proxy. 1.795 + * A CONNECT request for tunnelling through the proxy will 1.796 + * be created and sent, the response received and checked. 1.797 + * This method does <i>not</i> update the connection with 1.798 + * information about the tunnel, that is left to the caller. 1.799 + * 1.800 + * @param route the route to establish 1.801 + * @param context the context for request execution 1.802 + * 1.803 + * @return <code>true</code> if the tunnelled route is secure, 1.804 + * <code>false</code> otherwise. 1.805 + * The implementation here always returns <code>false</code>, 1.806 + * but derived classes may override. 1.807 + * 1.808 + * @throws HttpException in case of a problem 1.809 + * @throws IOException in case of an IO problem 1.810 + */ 1.811 + protected boolean createTunnelToTarget(HttpRoute route, 1.812 + HttpContext context) 1.813 + throws HttpException, IOException { 1.814 + 1.815 + HttpHost proxy = route.getProxyHost(); 1.816 + HttpHost target = route.getTargetHost(); 1.817 + HttpResponse response = null; 1.818 + 1.819 + boolean done = false; 1.820 + while (!done) { 1.821 + 1.822 + done = true; 1.823 + 1.824 + if (!this.managedConn.isOpen()) { 1.825 + this.managedConn.open(route, context, this.params); 1.826 + } 1.827 + 1.828 + HttpRequest connect = createConnectRequest(route, context); 1.829 + connect.setParams(this.params); 1.830 + 1.831 + // Populate the execution context 1.832 + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, 1.833 + target); 1.834 + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, 1.835 + proxy); 1.836 + context.setAttribute(ExecutionContext.HTTP_CONNECTION, 1.837 + managedConn); 1.838 + context.setAttribute(ClientContext.TARGET_AUTH_STATE, 1.839 + targetAuthState); 1.840 + context.setAttribute(ClientContext.PROXY_AUTH_STATE, 1.841 + proxyAuthState); 1.842 + context.setAttribute(ExecutionContext.HTTP_REQUEST, 1.843 + connect); 1.844 + 1.845 + this.requestExec.preProcess(connect, this.httpProcessor, context); 1.846 + 1.847 + response = this.requestExec.execute(connect, this.managedConn, context); 1.848 + 1.849 + response.setParams(this.params); 1.850 + this.requestExec.postProcess(response, this.httpProcessor, context); 1.851 + 1.852 + int status = response.getStatusLine().getStatusCode(); 1.853 + if (status < 200) { 1.854 + throw new HttpException("Unexpected response to CONNECT request: " + 1.855 + response.getStatusLine()); 1.856 + } 1.857 + 1.858 + CredentialsProvider credsProvider = (CredentialsProvider) 1.859 + context.getAttribute(ClientContext.CREDS_PROVIDER); 1.860 + 1.861 + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { 1.862 + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { 1.863 + 1.864 + this.log.debug("Proxy requested authentication"); 1.865 + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( 1.866 + response, context); 1.867 + try { 1.868 + processChallenges( 1.869 + challenges, this.proxyAuthState, this.proxyAuthHandler, 1.870 + response, context); 1.871 + } catch (AuthenticationException ex) { 1.872 + if (this.log.isWarnEnabled()) { 1.873 + this.log.warn("Authentication error: " + ex.getMessage()); 1.874 + break; 1.875 + } 1.876 + } 1.877 + updateAuthState(this.proxyAuthState, proxy, credsProvider); 1.878 + 1.879 + if (this.proxyAuthState.getCredentials() != null) { 1.880 + done = false; 1.881 + 1.882 + // Retry request 1.883 + if (this.reuseStrategy.keepAlive(response, context)) { 1.884 + this.log.debug("Connection kept alive"); 1.885 + // Consume response content 1.886 + HttpEntity entity = response.getEntity(); 1.887 + EntityUtils.consume(entity); 1.888 + } else { 1.889 + this.managedConn.close(); 1.890 + } 1.891 + 1.892 + } 1.893 + 1.894 + } else { 1.895 + // Reset proxy auth scope 1.896 + this.proxyAuthState.setAuthScope(null); 1.897 + } 1.898 + } 1.899 + } 1.900 + 1.901 + int status = response.getStatusLine().getStatusCode(); // can't be null 1.902 + 1.903 + if (status > 299) { 1.904 + 1.905 + // Buffer response content 1.906 + HttpEntity entity = response.getEntity(); 1.907 + if (entity != null) { 1.908 + response.setEntity(new BufferedHttpEntity(entity)); 1.909 + } 1.910 + 1.911 + this.managedConn.close(); 1.912 + throw new TunnelRefusedException("CONNECT refused by proxy: " + 1.913 + response.getStatusLine(), response); 1.914 + } 1.915 + 1.916 + this.managedConn.markReusable(); 1.917 + 1.918 + // How to decide on security of the tunnelled connection? 1.919 + // The socket factory knows only about the segment to the proxy. 1.920 + // Even if that is secure, the hop to the target may be insecure. 1.921 + // Leave it to derived classes, consider insecure by default here. 1.922 + return false; 1.923 + 1.924 + } // createTunnelToTarget 1.925 + 1.926 + 1.927 + 1.928 + /** 1.929 + * Creates a tunnel to an intermediate proxy. 1.930 + * This method is <i>not</i> implemented in this class. 1.931 + * It just throws an exception here. 1.932 + * 1.933 + * @param route the route to establish 1.934 + * @param hop the hop in the route to establish now. 1.935 + * <code>route.getHopTarget(hop)</code> 1.936 + * will return the proxy to tunnel to. 1.937 + * @param context the context for request execution 1.938 + * 1.939 + * @return <code>true</code> if the partially tunnelled connection 1.940 + * is secure, <code>false</code> otherwise. 1.941 + * 1.942 + * @throws HttpException in case of a problem 1.943 + * @throws IOException in case of an IO problem 1.944 + */ 1.945 + protected boolean createTunnelToProxy(HttpRoute route, int hop, 1.946 + HttpContext context) 1.947 + throws HttpException, IOException { 1.948 + 1.949 + // Have a look at createTunnelToTarget and replicate the parts 1.950 + // you need in a custom derived class. If your proxies don't require 1.951 + // authentication, it is not too hard. But for the stock version of 1.952 + // HttpClient, we cannot make such simplifying assumptions and would 1.953 + // have to include proxy authentication code. The HttpComponents team 1.954 + // is currently not in a position to support rarely used code of this 1.955 + // complexity. Feel free to submit patches that refactor the code in 1.956 + // createTunnelToTarget to facilitate re-use for proxy tunnelling. 1.957 + 1.958 + throw new HttpException("Proxy chains are not supported."); 1.959 + } 1.960 + 1.961 + 1.962 + 1.963 + /** 1.964 + * Creates the CONNECT request for tunnelling. 1.965 + * Called by {@link #createTunnelToTarget createTunnelToTarget}. 1.966 + * 1.967 + * @param route the route to establish 1.968 + * @param context the context for request execution 1.969 + * 1.970 + * @return the CONNECT request for tunnelling 1.971 + */ 1.972 + protected HttpRequest createConnectRequest(HttpRoute route, 1.973 + HttpContext context) { 1.974 + // see RFC 2817, section 5.2 and 1.975 + // INTERNET-DRAFT: Tunneling TCP based protocols through 1.976 + // Web proxy servers 1.977 + 1.978 + HttpHost target = route.getTargetHost(); 1.979 + 1.980 + String host = target.getHostName(); 1.981 + int port = target.getPort(); 1.982 + if (port < 0) { 1.983 + Scheme scheme = connManager.getSchemeRegistry(). 1.984 + getScheme(target.getSchemeName()); 1.985 + port = scheme.getDefaultPort(); 1.986 + } 1.987 + 1.988 + StringBuilder buffer = new StringBuilder(host.length() + 6); 1.989 + buffer.append(host); 1.990 + buffer.append(':'); 1.991 + buffer.append(Integer.toString(port)); 1.992 + 1.993 + String authority = buffer.toString(); 1.994 + ProtocolVersion ver = HttpProtocolParams.getVersion(params); 1.995 + HttpRequest req = new BasicHttpRequest 1.996 + ("CONNECT", authority, ver); 1.997 + 1.998 + return req; 1.999 + } 1.1000 + 1.1001 + 1.1002 + /** 1.1003 + * Analyzes a response to check need for a followup. 1.1004 + * 1.1005 + * @param roureq the request and route. 1.1006 + * @param response the response to analayze 1.1007 + * @param context the context used for the current request execution 1.1008 + * 1.1009 + * @return the followup request and route if there is a followup, or 1.1010 + * <code>null</code> if the response should be returned as is 1.1011 + * 1.1012 + * @throws HttpException in case of a problem 1.1013 + * @throws IOException in case of an IO problem 1.1014 + */ 1.1015 + protected RoutedRequest handleResponse(RoutedRequest roureq, 1.1016 + HttpResponse response, 1.1017 + HttpContext context) 1.1018 + throws HttpException, IOException { 1.1019 + 1.1020 + HttpRoute route = roureq.getRoute(); 1.1021 + RequestWrapper request = roureq.getRequest(); 1.1022 + 1.1023 + HttpParams params = request.getParams(); 1.1024 + if (HttpClientParams.isRedirecting(params) && 1.1025 + this.redirectStrategy.isRedirected(request, response, context)) { 1.1026 + 1.1027 + if (redirectCount >= maxRedirects) { 1.1028 + throw new RedirectException("Maximum redirects (" 1.1029 + + maxRedirects + ") exceeded"); 1.1030 + } 1.1031 + redirectCount++; 1.1032 + 1.1033 + // Virtual host cannot be used any longer 1.1034 + virtualHost = null; 1.1035 + 1.1036 + HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context); 1.1037 + HttpRequest orig = request.getOriginal(); 1.1038 + redirect.setHeaders(orig.getAllHeaders()); 1.1039 + 1.1040 + URI uri = redirect.getURI(); 1.1041 + if (uri.getHost() == null) { 1.1042 + throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri); 1.1043 + } 1.1044 + 1.1045 + HttpHost newTarget = new HttpHost( 1.1046 + uri.getHost(), 1.1047 + uri.getPort(), 1.1048 + uri.getScheme()); 1.1049 + 1.1050 + // Unset auth scope 1.1051 + targetAuthState.setAuthScope(null); 1.1052 + proxyAuthState.setAuthScope(null); 1.1053 + 1.1054 + // Invalidate auth states if redirecting to another host 1.1055 + if (!route.getTargetHost().equals(newTarget)) { 1.1056 + targetAuthState.invalidate(); 1.1057 + AuthScheme authScheme = proxyAuthState.getAuthScheme(); 1.1058 + if (authScheme != null && authScheme.isConnectionBased()) { 1.1059 + proxyAuthState.invalidate(); 1.1060 + } 1.1061 + } 1.1062 + 1.1063 + RequestWrapper wrapper = wrapRequest(redirect); 1.1064 + wrapper.setParams(params); 1.1065 + 1.1066 + HttpRoute newRoute = determineRoute(newTarget, wrapper, context); 1.1067 + RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); 1.1068 + 1.1069 + if (this.log.isDebugEnabled()) { 1.1070 + this.log.debug("Redirecting to '" + uri + "' via " + newRoute); 1.1071 + } 1.1072 + 1.1073 + return newRequest; 1.1074 + } 1.1075 + 1.1076 + CredentialsProvider credsProvider = (CredentialsProvider) 1.1077 + context.getAttribute(ClientContext.CREDS_PROVIDER); 1.1078 + 1.1079 + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { 1.1080 + 1.1081 + if (this.targetAuthHandler.isAuthenticationRequested(response, context)) { 1.1082 + 1.1083 + HttpHost target = (HttpHost) 1.1084 + context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); 1.1085 + if (target == null) { 1.1086 + target = route.getTargetHost(); 1.1087 + } 1.1088 + 1.1089 + this.log.debug("Target requested authentication"); 1.1090 + Map<String, Header> challenges = this.targetAuthHandler.getChallenges( 1.1091 + response, context); 1.1092 + try { 1.1093 + processChallenges(challenges, 1.1094 + this.targetAuthState, this.targetAuthHandler, 1.1095 + response, context); 1.1096 + } catch (AuthenticationException ex) { 1.1097 + if (this.log.isWarnEnabled()) { 1.1098 + this.log.warn("Authentication error: " + ex.getMessage()); 1.1099 + return null; 1.1100 + } 1.1101 + } 1.1102 + updateAuthState(this.targetAuthState, target, credsProvider); 1.1103 + 1.1104 + if (this.targetAuthState.getCredentials() != null) { 1.1105 + // Re-try the same request via the same route 1.1106 + return roureq; 1.1107 + } else { 1.1108 + return null; 1.1109 + } 1.1110 + } else { 1.1111 + // Reset target auth scope 1.1112 + this.targetAuthState.setAuthScope(null); 1.1113 + } 1.1114 + 1.1115 + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { 1.1116 + 1.1117 + HttpHost proxy = route.getProxyHost(); 1.1118 + 1.1119 + this.log.debug("Proxy requested authentication"); 1.1120 + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( 1.1121 + response, context); 1.1122 + try { 1.1123 + processChallenges(challenges, 1.1124 + this.proxyAuthState, this.proxyAuthHandler, 1.1125 + response, context); 1.1126 + } catch (AuthenticationException ex) { 1.1127 + if (this.log.isWarnEnabled()) { 1.1128 + this.log.warn("Authentication error: " + ex.getMessage()); 1.1129 + return null; 1.1130 + } 1.1131 + } 1.1132 + updateAuthState(this.proxyAuthState, proxy, credsProvider); 1.1133 + 1.1134 + if (this.proxyAuthState.getCredentials() != null) { 1.1135 + // Re-try the same request via the same route 1.1136 + return roureq; 1.1137 + } else { 1.1138 + return null; 1.1139 + } 1.1140 + } else { 1.1141 + // Reset proxy auth scope 1.1142 + this.proxyAuthState.setAuthScope(null); 1.1143 + } 1.1144 + } 1.1145 + return null; 1.1146 + } // handleResponse 1.1147 + 1.1148 + 1.1149 + /** 1.1150 + * Shuts down the connection. 1.1151 + * This method is called from a <code>catch</code> block in 1.1152 + * {@link #execute execute} during exception handling. 1.1153 + */ 1.1154 + private void abortConnection() { 1.1155 + ManagedClientConnection mcc = managedConn; 1.1156 + if (mcc != null) { 1.1157 + // we got here as the result of an exception 1.1158 + // no response will be returned, release the connection 1.1159 + managedConn = null; 1.1160 + try { 1.1161 + mcc.abortConnection(); 1.1162 + } catch (IOException ex) { 1.1163 + if (this.log.isDebugEnabled()) { 1.1164 + this.log.debug(ex.getMessage(), ex); 1.1165 + } 1.1166 + } 1.1167 + // ensure the connection manager properly releases this connection 1.1168 + try { 1.1169 + mcc.releaseConnection(); 1.1170 + } catch(IOException ignored) { 1.1171 + this.log.debug("Error releasing connection", ignored); 1.1172 + } 1.1173 + } 1.1174 + } // abortConnection 1.1175 + 1.1176 + 1.1177 + private void processChallenges( 1.1178 + final Map<String, Header> challenges, 1.1179 + final AuthState authState, 1.1180 + final AuthenticationHandler authHandler, 1.1181 + final HttpResponse response, 1.1182 + final HttpContext context) 1.1183 + throws MalformedChallengeException, AuthenticationException { 1.1184 + 1.1185 + AuthScheme authScheme = authState.getAuthScheme(); 1.1186 + if (authScheme == null) { 1.1187 + // Authentication not attempted before 1.1188 + authScheme = authHandler.selectScheme(challenges, response, context); 1.1189 + authState.setAuthScheme(authScheme); 1.1190 + } 1.1191 + String id = authScheme.getSchemeName(); 1.1192 + 1.1193 + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); 1.1194 + if (challenge == null) { 1.1195 + throw new AuthenticationException(id + 1.1196 + " authorization challenge expected, but not found"); 1.1197 + } 1.1198 + authScheme.processChallenge(challenge); 1.1199 + this.log.debug("Authorization challenge processed"); 1.1200 + } 1.1201 + 1.1202 + 1.1203 + private void updateAuthState( 1.1204 + final AuthState authState, 1.1205 + final HttpHost host, 1.1206 + final CredentialsProvider credsProvider) { 1.1207 + 1.1208 + if (!authState.isValid()) { 1.1209 + return; 1.1210 + } 1.1211 + 1.1212 + String hostname = host.getHostName(); 1.1213 + int port = host.getPort(); 1.1214 + if (port < 0) { 1.1215 + Scheme scheme = connManager.getSchemeRegistry().getScheme(host); 1.1216 + port = scheme.getDefaultPort(); 1.1217 + } 1.1218 + 1.1219 + AuthScheme authScheme = authState.getAuthScheme(); 1.1220 + AuthScope authScope = new AuthScope( 1.1221 + hostname, 1.1222 + port, 1.1223 + authScheme.getRealm(), 1.1224 + authScheme.getSchemeName()); 1.1225 + 1.1226 + if (this.log.isDebugEnabled()) { 1.1227 + this.log.debug("Authentication scope: " + authScope); 1.1228 + } 1.1229 + Credentials creds = authState.getCredentials(); 1.1230 + if (creds == null) { 1.1231 + creds = credsProvider.getCredentials(authScope); 1.1232 + if (this.log.isDebugEnabled()) { 1.1233 + if (creds != null) { 1.1234 + this.log.debug("Found credentials"); 1.1235 + } else { 1.1236 + this.log.debug("Credentials not found"); 1.1237 + } 1.1238 + } 1.1239 + } else { 1.1240 + if (authScheme.isComplete()) { 1.1241 + this.log.debug("Authentication failed"); 1.1242 + creds = null; 1.1243 + } 1.1244 + } 1.1245 + authState.setAuthScope(authScope); 1.1246 + authState.setCredentials(creds); 1.1247 + } 1.1248 + 1.1249 + private void invalidateAuthIfSuccessful(final AuthState authState) { 1.1250 + AuthScheme authscheme = authState.getAuthScheme(); 1.1251 + if (authscheme != null 1.1252 + && authscheme.isConnectionBased() 1.1253 + && authscheme.isComplete() 1.1254 + && authState.getCredentials() != null) { 1.1255 + authState.invalidate(); 1.1256 + } 1.1257 + } 1.1258 + 1.1259 +} // class DefaultClientRequestDirector