mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial