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()) 436 && (!isCleartextTrafficPermitted( 437 route.getTargetHost().getHostName()))) { 438 throw new IOException( 439 "Cleartext traffic not permitted: " + route.getTargetHost()); 440 } 441 // END android-added 442 response = requestExec.execute(wrapper, managedConn, context); 443 retrying = false; 444 445 } catch (IOException ex) { 446 this.log.debug("Closing the connection."); 447 managedConn.close(); 448 if (retryHandler.retryRequest(ex, execCount, context)) { 449 if (this.log.isInfoEnabled()) { 450 this.log.info("I/O exception ("+ ex.getClass().getName() + 451 ") caught when processing request: " 452 + ex.getMessage()); 453 } 454 if (this.log.isDebugEnabled()) { 455 this.log.debug(ex.getMessage(), ex); 456 } 457 this.log.info("Retrying request"); 458 } else { 459 throw ex; 460 } 461 462 // If we have a direct route to the target host 463 // just re-open connection and re-try the request 464 if (route.getHopCount() == 1) { 465 this.log.debug("Reopening the direct connection."); 466 managedConn.open(route, context, params); 467 } else { 468 // otherwise give up 469 throw ex; 470 } 471 472 } 473 474 } 475 476 // Run response protocol interceptors 477 response.setParams(params); 478 requestExec.postProcess(response, httpProcessor, context); 479 480 481 // The connection is in or can be brought to a re-usable state. 482 reuse = reuseStrategy.keepAlive(response, context); 483 if(reuse) { 484 // Set the idle duration of this connection 485 long duration = keepAliveStrategy.getKeepAliveDuration(response, context); 486 managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); 487 } 488 489 RoutedRequest followup = handleResponse(roureq, response, context); 490 if (followup == null) { 491 done = true; 492 } else { 493 if (reuse) { 494 this.log.debug("Connection kept alive"); 495 // Make sure the response body is fully consumed, if present 496 HttpEntity entity = response.getEntity(); 497 if (entity != null) { 498 entity.consumeContent(); 499 } 500 // entity consumed above is not an auto-release entity, 501 // need to mark the connection re-usable explicitly 502 managedConn.markReusable(); 503 } else { 504 managedConn.close(); 505 } 506 // check if we can use the same connection for the followup 507 if (!followup.getRoute().equals(roureq.getRoute())) { 508 releaseConnection(); 509 } 510 roureq = followup; 511 } 512 513 userToken = this.userTokenHandler.getUserToken(context); 514 context.setAttribute(ClientContext.USER_TOKEN, userToken); 515 if (managedConn != null) { 516 managedConn.setState(userToken); 517 } 518 } // while not done 519 520 521 // check for entity, release connection if possible 522 if ((response == null) || (response.getEntity() == null) || 523 !response.getEntity().isStreaming()) { 524 // connection not needed and (assumed to be) in re-usable state 525 if (reuse) 526 managedConn.markReusable(); 527 releaseConnection(); 528 } else { 529 // install an auto-release entity 530 HttpEntity entity = response.getEntity(); 531 entity = new BasicManagedEntity(entity, managedConn, reuse); 532 response.setEntity(entity); 533 } 534 535 return response; 536 537 } catch (HttpException ex) { 538 abortConnection(); 539 throw ex; 540 } catch (IOException ex) { 541 abortConnection(); 542 throw ex; 543 } catch (RuntimeException ex) { 544 abortConnection(); 545 throw ex; 546 } 547 } // execute 548 549 /** 550 * Returns the connection back to the connection manager 551 * and prepares for retrieving a new connection during 552 * the next request. 553 */ 554 protected void releaseConnection() { 555 // Release the connection through the ManagedConnection instead of the 556 // ConnectionManager directly. This lets the connection control how 557 // it is released. 558 try { 559 managedConn.releaseConnection(); 560 } catch(IOException ignored) { 561 this.log.debug("IOException releasing connection", ignored); 562 } 563 managedConn = null; 564 } 565 566 /** 567 * Determines the route for a request. 568 * Called by {@link #execute} 569 * to determine the route for either the original or a followup request. 570 * 571 * @param target the target host for the request. 572 * Implementations may accept <code>null</code> 573 * if they can still determine a route, for example 574 * to a default target or by inspecting the request. 575 * @param request the request to execute 576 * @param context the context to use for the execution, 577 * never <code>null</code> 578 * 579 * @return the route the request should take 580 * 581 * @throws HttpException in case of a problem 582 */ 583 protected HttpRoute determineRoute(HttpHost target, 584 HttpRequest request, 585 HttpContext context) 586 throws HttpException { 587 588 if (target == null) { 589 target = (HttpHost) request.getParams().getParameter( 590 ClientPNames.DEFAULT_HOST); 591 } 592 if (target == null) { 593 // BEGIN android-changed 594 // If the URI was malformed, make it obvious where there's no host component 595 String scheme = null; 596 String host = null; 597 String path = null; 598 URI uri; 599 if (request instanceof HttpUriRequest 600 && (uri = ((HttpUriRequest) request).getURI()) != null) { 601 scheme = uri.getScheme(); 602 host = uri.getHost(); 603 path = uri.getPath(); 604 } 605 throw new IllegalStateException( "Target host must not be null, or set in parameters." 606 + " scheme=" + scheme + ", host=" + host + ", path=" + path); 607 // END android-changed 608 } 609 610 return this.routePlanner.determineRoute(target, request, context); 611 } 612 613 614 /** 615 * Establishes the target route. 616 * 617 * @param route the route to establish 618 * @param context the context for the request execution 619 * 620 * @throws HttpException in case of a problem 621 * @throws IOException in case of an IO problem 622 */ 623 protected void establishRoute(HttpRoute route, HttpContext context) 624 throws HttpException, IOException { 625 626 //@@@ how to handle CONNECT requests for tunnelling? 627 //@@@ refuse to send external CONNECT via director? special handling? 628 629 //@@@ should the request parameters already be used below? 630 //@@@ probably yes, but they're not linked yet 631 //@@@ will linking above cause problems with linking in reqExec? 632 //@@@ probably not, because the parent is replaced 633 //@@@ just make sure we don't link parameters to themselves 634 635 HttpRouteDirector rowdy = new BasicRouteDirector(); 636 int step; 637 do { 638 HttpRoute fact = managedConn.getRoute(); 639 step = rowdy.nextStep(route, fact); 640 641 switch (step) { 642 643 case HttpRouteDirector.CONNECT_TARGET: 644 case HttpRouteDirector.CONNECT_PROXY: 645 managedConn.open(route, context, this.params); 646 break; 647 648 case HttpRouteDirector.TUNNEL_TARGET: { 649 boolean secure = createTunnelToTarget(route, context); 650 this.log.debug("Tunnel to target created."); 651 managedConn.tunnelTarget(secure, this.params); 652 } break; 653 654 case HttpRouteDirector.TUNNEL_PROXY: { 655 // The most simple example for this case is a proxy chain 656 // of two proxies, where P1 must be tunnelled to P2. 657 // route: Source -> P1 -> P2 -> Target (3 hops) 658 // fact: Source -> P1 -> Target (2 hops) 659 final int hop = fact.getHopCount()-1; // the hop to establish 660 boolean secure = createTunnelToProxy(route, hop, context); 661 this.log.debug("Tunnel to proxy created."); 662 managedConn.tunnelProxy(route.getHopTarget(hop), 663 secure, this.params); 664 } break; 665 666 667 case HttpRouteDirector.LAYER_PROTOCOL: 668 managedConn.layerProtocol(context, this.params); 669 break; 670 671 case HttpRouteDirector.UNREACHABLE: 672 throw new IllegalStateException 673 ("Unable to establish route." + 674 "\nplanned = " + route + 675 "\ncurrent = " + fact); 676 677 case HttpRouteDirector.COMPLETE: 678 // do nothing 679 break; 680 681 default: 682 throw new IllegalStateException 683 ("Unknown step indicator "+step+" from RouteDirector."); 684 } // switch 685 686 } while (step > HttpRouteDirector.COMPLETE); 687 688 } // establishConnection 689 690 691 /** 692 * Creates a tunnel to the target server. 693 * The connection must be established to the (last) proxy. 694 * A CONNECT request for tunnelling through the proxy will 695 * be created and sent, the response received and checked. 696 * This method does <i>not</i> update the connection with 697 * information about the tunnel, that is left to the caller. 698 * 699 * @param route the route to establish 700 * @param context the context for request execution 701 * 702 * @return <code>true</code> if the tunnelled route is secure, 703 * <code>false</code> otherwise. 704 * The implementation here always returns <code>false</code>, 705 * but derived classes may override. 706 * 707 * @throws HttpException in case of a problem 708 * @throws IOException in case of an IO problem 709 */ 710 protected boolean createTunnelToTarget(HttpRoute route, 711 HttpContext context) 712 throws HttpException, IOException { 713 714 HttpHost proxy = route.getProxyHost(); 715 HttpHost target = route.getTargetHost(); 716 HttpResponse response = null; 717 718 boolean done = false; 719 while (!done) { 720 721 done = true; 722 723 if (!this.managedConn.isOpen()) { 724 this.managedConn.open(route, context, this.params); 725 } 726 727 HttpRequest connect = createConnectRequest(route, context); 728 729 String agent = HttpProtocolParams.getUserAgent(params); 730 if (agent != null) { 731 connect.addHeader(HTTP.USER_AGENT, agent); 732 } 733 connect.addHeader(HTTP.TARGET_HOST, target.toHostString()); 734 735 AuthScheme authScheme = this.proxyAuthState.getAuthScheme(); 736 AuthScope authScope = this.proxyAuthState.getAuthScope(); 737 Credentials creds = this.proxyAuthState.getCredentials(); 738 if (creds != null) { 739 if (authScope != null || !authScheme.isConnectionBased()) { 740 try { 741 connect.addHeader(authScheme.authenticate(creds, connect)); 742 } catch (AuthenticationException ex) { 743 if (this.log.isErrorEnabled()) { 744 this.log.error("Proxy authentication error: " + ex.getMessage()); 745 } 746 } 747 } 748 } 749 750 response = requestExec.execute(connect, this.managedConn, context); 751 752 int status = response.getStatusLine().getStatusCode(); 753 if (status < 200) { 754 throw new HttpException("Unexpected response to CONNECT request: " + 755 response.getStatusLine()); 756 } 757 758 CredentialsProvider credsProvider = (CredentialsProvider) 759 context.getAttribute(ClientContext.CREDS_PROVIDER); 760 761 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { 762 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { 763 764 this.log.debug("Proxy requested authentication"); 765 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( 766 response, context); 767 try { 768 processChallenges( 769 challenges, this.proxyAuthState, this.proxyAuthHandler, 770 response, context); 771 } catch (AuthenticationException ex) { 772 if (this.log.isWarnEnabled()) { 773 this.log.warn("Authentication error: " + ex.getMessage()); 774 break; 775 } 776 } 777 updateAuthState(this.proxyAuthState, proxy, credsProvider); 778 779 if (this.proxyAuthState.getCredentials() != null) { 780 done = false; 781 782 // Retry request 783 if (this.reuseStrategy.keepAlive(response, context)) { 784 this.log.debug("Connection kept alive"); 785 // Consume response content 786 HttpEntity entity = response.getEntity(); 787 if (entity != null) { 788 entity.consumeContent(); 789 } 790 } else { 791 this.managedConn.close(); 792 } 793 794 } 795 796 } else { 797 // Reset proxy auth scope 798 this.proxyAuthState.setAuthScope(null); 799 } 800 } 801 } 802 803 int status = response.getStatusLine().getStatusCode(); 804 805 if (status > 299) { 806 807 // Buffer response content 808 HttpEntity entity = response.getEntity(); 809 if (entity != null) { 810 response.setEntity(new BufferedHttpEntity(entity)); 811 } 812 813 this.managedConn.close(); 814 throw new TunnelRefusedException("CONNECT refused by proxy: " + 815 response.getStatusLine(), response); 816 } 817 818 this.managedConn.markReusable(); 819 820 // How to decide on security of the tunnelled connection? 821 // The socket factory knows only about the segment to the proxy. 822 // Even if that is secure, the hop to the target may be insecure. 823 // Leave it to derived classes, consider insecure by default here. 824 return false; 825 826 } // createTunnelToTarget 827 828 829 830 /** 831 * Creates a tunnel to an intermediate proxy. 832 * This method is <i>not</i> implemented in this class. 833 * It just throws an exception here. 834 * 835 * @param route the route to establish 836 * @param hop the hop in the route to establish now. 837 * <code>route.getHopTarget(hop)</code> 838 * will return the proxy to tunnel to. 839 * @param context the context for request execution 840 * 841 * @return <code>true</code> if the partially tunnelled connection 842 * is secure, <code>false</code> otherwise. 843 * 844 * @throws HttpException in case of a problem 845 * @throws IOException in case of an IO problem 846 */ 847 protected boolean createTunnelToProxy(HttpRoute route, int hop, 848 HttpContext context) 849 throws HttpException, IOException { 850 851 // Have a look at createTunnelToTarget and replicate the parts 852 // you need in a custom derived class. If your proxies don't require 853 // authentication, it is not too hard. But for the stock version of 854 // HttpClient, we cannot make such simplifying assumptions and would 855 // have to include proxy authentication code. The HttpComponents team 856 // is currently not in a position to support rarely used code of this 857 // complexity. Feel free to submit patches that refactor the code in 858 // createTunnelToTarget to facilitate re-use for proxy tunnelling. 859 860 throw new UnsupportedOperationException 861 ("Proxy chains are not supported."); 862 } 863 864 865 866 /** 867 * Creates the CONNECT request for tunnelling. 868 * Called by {@link #createTunnelToTarget createTunnelToTarget}. 869 * 870 * @param route the route to establish 871 * @param context the context for request execution 872 * 873 * @return the CONNECT request for tunnelling 874 */ 875 protected HttpRequest createConnectRequest(HttpRoute route, 876 HttpContext context) { 877 // see RFC 2817, section 5.2 and 878 // INTERNET-DRAFT: Tunneling TCP based protocols through 879 // Web proxy servers 880 881 HttpHost target = route.getTargetHost(); 882 883 String host = target.getHostName(); 884 int port = target.getPort(); 885 if (port < 0) { 886 Scheme scheme = connManager.getSchemeRegistry(). 887 getScheme(target.getSchemeName()); 888 port = scheme.getDefaultPort(); 889 } 890 891 StringBuilder buffer = new StringBuilder(host.length() + 6); 892 buffer.append(host); 893 buffer.append(':'); 894 buffer.append(Integer.toString(port)); 895 896 String authority = buffer.toString(); 897 ProtocolVersion ver = HttpProtocolParams.getVersion(params); 898 HttpRequest req = new BasicHttpRequest 899 ("CONNECT", authority, ver); 900 901 return req; 902 } 903 904 905 /** 906 * Analyzes a response to check need for a followup. 907 * 908 * @param roureq the request and route. 909 * @param response the response to analayze 910 * @param context the context used for the current request execution 911 * 912 * @return the followup request and route if there is a followup, or 913 * <code>null</code> if the response should be returned as is 914 * 915 * @throws HttpException in case of a problem 916 * @throws IOException in case of an IO problem 917 */ 918 protected RoutedRequest handleResponse(RoutedRequest roureq, 919 HttpResponse response, 920 HttpContext context) 921 throws HttpException, IOException { 922 923 HttpRoute route = roureq.getRoute(); 924 HttpHost proxy = route.getProxyHost(); 925 RequestWrapper request = roureq.getRequest(); 926 927 HttpParams params = request.getParams(); 928 if (HttpClientParams.isRedirecting(params) && 929 this.redirectHandler.isRedirectRequested(response, context)) { 930 931 if (redirectCount >= maxRedirects) { 932 throw new RedirectException("Maximum redirects (" 933 + maxRedirects + ") exceeded"); 934 } 935 redirectCount++; 936 937 URI uri = this.redirectHandler.getLocationURI(response, context); 938 939 HttpHost newTarget = new HttpHost( 940 uri.getHost(), 941 uri.getPort(), 942 uri.getScheme()); 943 944 HttpGet redirect = new HttpGet(uri); 945 946 HttpRequest orig = request.getOriginal(); 947 redirect.setHeaders(orig.getAllHeaders()); 948 949 RequestWrapper wrapper = new RequestWrapper(redirect); 950 wrapper.setParams(params); 951 952 HttpRoute newRoute = determineRoute(newTarget, wrapper, context); 953 RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); 954 955 if (this.log.isDebugEnabled()) { 956 this.log.debug("Redirecting to '" + uri + "' via " + newRoute); 957 } 958 959 return newRequest; 960 } 961 962 CredentialsProvider credsProvider = (CredentialsProvider) 963 context.getAttribute(ClientContext.CREDS_PROVIDER); 964 965 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { 966 967 if (this.targetAuthHandler.isAuthenticationRequested(response, context)) { 968 969 HttpHost target = (HttpHost) 970 context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); 971 if (target == null) { 972 target = route.getTargetHost(); 973 } 974 975 this.log.debug("Target requested authentication"); 976 Map<String, Header> challenges = this.targetAuthHandler.getChallenges( 977 response, context); 978 try { 979 processChallenges(challenges, 980 this.targetAuthState, this.targetAuthHandler, 981 response, context); 982 } catch (AuthenticationException ex) { 983 if (this.log.isWarnEnabled()) { 984 this.log.warn("Authentication error: " + ex.getMessage()); 985 return null; 986 } 987 } 988 updateAuthState(this.targetAuthState, target, credsProvider); 989 990 if (this.targetAuthState.getCredentials() != null) { 991 // Re-try the same request via the same route 992 return roureq; 993 } else { 994 return null; 995 } 996 } else { 997 // Reset target auth scope 998 this.targetAuthState.setAuthScope(null); 999 } 1000 1001 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { 1002 1003 this.log.debug("Proxy requested authentication"); 1004 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( 1005 response, context); 1006 try { 1007 processChallenges(challenges, 1008 this.proxyAuthState, this.proxyAuthHandler, 1009 response, context); 1010 } catch (AuthenticationException ex) { 1011 if (this.log.isWarnEnabled()) { 1012 this.log.warn("Authentication error: " + ex.getMessage()); 1013 return null; 1014 } 1015 } 1016 updateAuthState(this.proxyAuthState, proxy, credsProvider); 1017 1018 if (this.proxyAuthState.getCredentials() != null) { 1019 // Re-try the same request via the same route 1020 return roureq; 1021 } else { 1022 return null; 1023 } 1024 } else { 1025 // Reset proxy auth scope 1026 this.proxyAuthState.setAuthScope(null); 1027 } 1028 } 1029 return null; 1030 } // handleResponse 1031 1032 1033 /** 1034 * Shuts down the connection. 1035 * This method is called from a <code>catch</code> block in 1036 * {@link #execute execute} during exception handling. 1037 */ 1038 private void abortConnection() { 1039 ManagedClientConnection mcc = managedConn; 1040 if (mcc != null) { 1041 // we got here as the result of an exception 1042 // no response will be returned, release the connection 1043 managedConn = null; 1044 try { 1045 mcc.abortConnection(); 1046 } catch (IOException ex) { 1047 if (this.log.isDebugEnabled()) { 1048 this.log.debug(ex.getMessage(), ex); 1049 } 1050 } 1051 // ensure the connection manager properly releases this connection 1052 try { 1053 mcc.releaseConnection(); 1054 } catch(IOException ignored) { 1055 this.log.debug("Error releasing connection", ignored); 1056 } 1057 } 1058 } // abortConnection 1059 1060 1061 private void processChallenges( 1062 final Map<String, Header> challenges, 1063 final AuthState authState, 1064 final AuthenticationHandler authHandler, 1065 final HttpResponse response, 1066 final HttpContext context) 1067 throws MalformedChallengeException, AuthenticationException { 1068 1069 AuthScheme authScheme = authState.getAuthScheme(); 1070 if (authScheme == null) { 1071 // Authentication not attempted before 1072 authScheme = authHandler.selectScheme(challenges, response, context); 1073 authState.setAuthScheme(authScheme); 1074 } 1075 String id = authScheme.getSchemeName(); 1076 1077 Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); 1078 if (challenge == null) { 1079 throw new AuthenticationException(id + 1080 " authorization challenge expected, but not found"); 1081 } 1082 authScheme.processChallenge(challenge); 1083 this.log.debug("Authorization challenge processed"); 1084 } 1085 1086 1087 private void updateAuthState( 1088 final AuthState authState, 1089 final HttpHost host, 1090 final CredentialsProvider credsProvider) { 1091 1092 if (!authState.isValid()) { 1093 return; 1094 } 1095 1096 String hostname = host.getHostName(); 1097 int port = host.getPort(); 1098 if (port < 0) { 1099 Scheme scheme = connManager.getSchemeRegistry().getScheme(host); 1100 port = scheme.getDefaultPort(); 1101 } 1102 1103 AuthScheme authScheme = authState.getAuthScheme(); 1104 AuthScope authScope = new AuthScope( 1105 hostname, 1106 port, 1107 authScheme.getRealm(), 1108 authScheme.getSchemeName()); 1109 1110 if (this.log.isDebugEnabled()) { 1111 this.log.debug("Authentication scope: " + authScope); 1112 } 1113 Credentials creds = authState.getCredentials(); 1114 if (creds == null) { 1115 creds = credsProvider.getCredentials(authScope); 1116 if (this.log.isDebugEnabled()) { 1117 if (creds != null) { 1118 this.log.debug("Found credentials"); 1119 } else { 1120 this.log.debug("Credentials not found"); 1121 } 1122 } 1123 } else { 1124 if (authScheme.isComplete()) { 1125 this.log.debug("Authentication failed"); 1126 creds = null; 1127 } 1128 } 1129 authState.setAuthScope(authScope); 1130 authState.setCredentials(creds); 1131 } 1132 1133 // BEGIN android-added 1134 /** Cached instance of android.security.NetworkSecurityPolicy. */ 1135 private static Object networkSecurityPolicy; 1136 1137 /** Cached android.security.NetworkSecurityPolicy.isCleartextTrafficPermitted method. */ 1138 private static Method cleartextTrafficPermittedMethod; 1139 1140 private static boolean isCleartextTrafficPermitted(String hostname) { 1141 // TODO: Remove this method once NetworkSecurityPolicy can be accessed without Reflection. 1142 // This method invokes NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted 1143 // via Reflection API. 1144 // Because of the way external/apache-http is built, in the near term it can't invoke new 1145 // Android framework API directly. 1146 try { 1147 Object policy; 1148 Method method; 1149 synchronized (DefaultRequestDirector.class) { 1150 if (cleartextTrafficPermittedMethod == null) { 1151 Class<?> cls = Class.forName("android.security.NetworkSecurityPolicy"); 1152 Method getInstanceMethod = cls.getMethod("getInstance"); 1153 networkSecurityPolicy = getInstanceMethod.invoke(null); 1154 cleartextTrafficPermittedMethod = 1155 cls.getMethod("isCleartextTrafficPermitted", String.class); 1156 } 1157 policy = networkSecurityPolicy; 1158 method = cleartextTrafficPermittedMethod; 1159 } 1160 return (Boolean) method.invoke(policy, hostname); 1161 } catch (ReflectiveOperationException e) { 1162 // Can't access the Android framework NetworkSecurityPolicy. To be backward compatible, 1163 // assume that cleartext traffic is permitted. Android CTS will take care of ensuring 1164 // this issue doesn't occur on new Android platforms. 1165 return true; 1166 } 1167 } 1168 // END android-added 1169 1170} // class DefaultClientRequestDirector 1171