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