• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.lang.reflect.Method;
37 import java.net.URI;
38 import java.net.URISyntaxException;
39 import java.util.Locale;
40 import java.util.Map;
41 import java.util.concurrent.TimeUnit;
42 
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.apache.http.ConnectionReuseStrategy;
46 import org.apache.http.Header;
47 import org.apache.http.HttpEntity;
48 import org.apache.http.HttpEntityEnclosingRequest;
49 import org.apache.http.HttpException;
50 import org.apache.http.HttpHost;
51 import org.apache.http.HttpRequest;
52 import org.apache.http.HttpResponse;
53 import org.apache.http.ProtocolException;
54 import org.apache.http.ProtocolVersion;
55 import org.apache.http.auth.AuthScheme;
56 import org.apache.http.auth.AuthScope;
57 import org.apache.http.auth.AuthState;
58 import org.apache.http.auth.AuthenticationException;
59 import org.apache.http.auth.Credentials;
60 import org.apache.http.auth.MalformedChallengeException;
61 import org.apache.http.client.AuthenticationHandler;
62 import org.apache.http.client.RequestDirector;
63 import org.apache.http.client.CredentialsProvider;
64 import org.apache.http.client.HttpRequestRetryHandler;
65 import org.apache.http.client.NonRepeatableRequestException;
66 import org.apache.http.client.RedirectException;
67 import org.apache.http.client.RedirectHandler;
68 import org.apache.http.client.UserTokenHandler;
69 import org.apache.http.client.methods.AbortableHttpRequest;
70 import org.apache.http.client.methods.HttpGet;
71 import org.apache.http.client.methods.HttpUriRequest;
72 import org.apache.http.client.params.ClientPNames;
73 import org.apache.http.client.params.HttpClientParams;
74 import org.apache.http.client.protocol.ClientContext;
75 import org.apache.http.client.utils.URIUtils;
76 import org.apache.http.conn.BasicManagedEntity;
77 import org.apache.http.conn.ClientConnectionManager;
78 import org.apache.http.conn.ClientConnectionRequest;
79 import org.apache.http.conn.ConnectionKeepAliveStrategy;
80 import org.apache.http.conn.ManagedClientConnection;
81 import org.apache.http.conn.params.ConnManagerParams;
82 import org.apache.http.conn.routing.BasicRouteDirector;
83 import org.apache.http.conn.routing.HttpRoute;
84 import org.apache.http.conn.routing.HttpRouteDirector;
85 import org.apache.http.conn.routing.HttpRoutePlanner;
86 import org.apache.http.conn.scheme.Scheme;
87 import org.apache.http.entity.BufferedHttpEntity;
88 import org.apache.http.message.BasicHttpRequest;
89 import org.apache.http.params.HttpConnectionParams;
90 import org.apache.http.params.HttpParams;
91 import org.apache.http.params.HttpProtocolParams;
92 import org.apache.http.protocol.ExecutionContext;
93 import org.apache.http.protocol.HTTP;
94 import org.apache.http.protocol.HttpContext;
95 import org.apache.http.protocol.HttpProcessor;
96 import 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
116 public 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 
DefaultRequestDirector( final HttpRequestExecutor requestExec, final ClientConnectionManager conman, final ConnectionReuseStrategy reustrat, final ConnectionKeepAliveStrategy kastrat, final HttpRoutePlanner rouplan, final HttpProcessor httpProcessor, final HttpRequestRetryHandler retryHandler, final RedirectHandler redirectHandler, final AuthenticationHandler targetAuthHandler, final AuthenticationHandler proxyAuthHandler, final UserTokenHandler userTokenHandler, final HttpParams params)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 
wrapRequest( final HttpRequest request)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 
rewriteRequestURI( final RequestWrapper request, final HttpRoute route)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
execute(HttpHost target, HttpRequest request, HttpContext context)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      */
releaseConnection()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      */
determineRoute(HttpHost target, HttpRequest request, HttpContext context)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      */
establishRoute(HttpRoute route, HttpContext context)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      */
createTunnelToTarget(HttpRoute route, HttpContext context)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      */
createTunnelToProxy(HttpRoute route, int hop, HttpContext context)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      */
createConnectRequest(HttpRoute route, HttpContext context)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      */
handleResponse(RoutedRequest roureq, HttpResponse response, HttpContext context)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      */
abortConnection()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 
processChallenges( final Map<String, Header> challenges, final AuthState authState, final AuthenticationHandler authHandler, final HttpResponse response, final HttpContext context)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 
updateAuthState( final AuthState authState, final HttpHost host, final CredentialsProvider credsProvider)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 
isCleartextTrafficPermitted()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