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