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