Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
28 package ch.boye.httpclientandroidlib.impl.client;
30 import java.io.IOException;
31 import java.io.InterruptedIOException;
32 import java.net.URI;
33 import java.net.URISyntaxException;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.concurrent.TimeUnit;
38 import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
40 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
41 /* LogFactory removed by HttpClient for Android script. */
42 import ch.boye.httpclientandroidlib.ConnectionReuseStrategy;
43 import ch.boye.httpclientandroidlib.Header;
44 import ch.boye.httpclientandroidlib.HttpEntity;
45 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
46 import ch.boye.httpclientandroidlib.HttpException;
47 import ch.boye.httpclientandroidlib.HttpHost;
48 import ch.boye.httpclientandroidlib.HttpRequest;
49 import ch.boye.httpclientandroidlib.HttpResponse;
50 import ch.boye.httpclientandroidlib.ProtocolException;
51 import ch.boye.httpclientandroidlib.ProtocolVersion;
52 import ch.boye.httpclientandroidlib.auth.AuthScheme;
53 import ch.boye.httpclientandroidlib.auth.AuthScope;
54 import ch.boye.httpclientandroidlib.auth.AuthState;
55 import ch.boye.httpclientandroidlib.auth.AuthenticationException;
56 import ch.boye.httpclientandroidlib.auth.Credentials;
57 import ch.boye.httpclientandroidlib.auth.MalformedChallengeException;
58 import ch.boye.httpclientandroidlib.client.AuthenticationHandler;
59 import ch.boye.httpclientandroidlib.client.RedirectStrategy;
60 import ch.boye.httpclientandroidlib.client.RequestDirector;
61 import ch.boye.httpclientandroidlib.client.CredentialsProvider;
62 import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler;
63 import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException;
64 import ch.boye.httpclientandroidlib.client.RedirectException;
65 import ch.boye.httpclientandroidlib.client.UserTokenHandler;
66 import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest;
67 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
68 import ch.boye.httpclientandroidlib.client.params.ClientPNames;
69 import ch.boye.httpclientandroidlib.client.params.HttpClientParams;
70 import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
71 import ch.boye.httpclientandroidlib.client.utils.URIUtils;
72 import ch.boye.httpclientandroidlib.conn.BasicManagedEntity;
73 import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
74 import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest;
75 import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy;
76 import ch.boye.httpclientandroidlib.conn.ManagedClientConnection;
77 import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams;
78 import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector;
79 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
80 import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector;
81 import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner;
82 import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
83 import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity;
84 import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException;
85 import ch.boye.httpclientandroidlib.message.BasicHttpRequest;
86 import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
87 import ch.boye.httpclientandroidlib.params.HttpParams;
88 import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
89 import ch.boye.httpclientandroidlib.protocol.ExecutionContext;
90 import ch.boye.httpclientandroidlib.protocol.HttpContext;
91 import ch.boye.httpclientandroidlib.protocol.HttpProcessor;
92 import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor;
93 import ch.boye.httpclientandroidlib.util.EntityUtils;
95 /**
96 * Default implementation of {@link RequestDirector}.
97 * <p>
98 * The following parameters can be used to customize the behavior of this
99 * class:
100 * <ul>
101 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
102 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
103 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
104 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
105 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
106 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li>
107 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
108 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
109 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
110 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li>
111 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li>
112 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li>
113 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li>
114 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
115 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
116 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
117 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
118 * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
119 * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
120 * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
121 * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
122 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li>
123 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
124 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
125 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li>
126 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
127 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li>
128 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li>
129 * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li>
130 * </ul>
131 *
132 * @since 4.0
133 */
134 @SuppressWarnings("deprecation")
135 @NotThreadSafe // e.g. managedConn
136 public class DefaultRequestDirector implements RequestDirector {
138 public HttpClientAndroidLog log;
140 /** The connection manager. */
141 protected final ClientConnectionManager connManager;
143 /** The route planner. */
144 protected final HttpRoutePlanner routePlanner;
146 /** The connection re-use strategy. */
147 protected final ConnectionReuseStrategy reuseStrategy;
149 /** The keep-alive duration strategy. */
150 protected final ConnectionKeepAliveStrategy keepAliveStrategy;
152 /** The request executor. */
153 protected final HttpRequestExecutor requestExec;
155 /** The HTTP protocol processor. */
156 protected final HttpProcessor httpProcessor;
158 /** The request retry handler. */
159 protected final HttpRequestRetryHandler retryHandler;
161 /** The redirect handler. */
162 @Deprecated
163 protected final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler = null;
165 /** The redirect strategy. */
166 protected final RedirectStrategy redirectStrategy;
168 /** The target authentication handler. */
169 protected final AuthenticationHandler targetAuthHandler;
171 /** The proxy authentication handler. */
172 protected final AuthenticationHandler proxyAuthHandler;
174 /** The user token handler. */
175 protected final UserTokenHandler userTokenHandler;
177 /** The HTTP parameters. */
178 protected final HttpParams params;
180 /** The currently allocated connection. */
181 protected ManagedClientConnection managedConn;
183 protected final AuthState targetAuthState;
185 protected final AuthState proxyAuthState;
187 private int execCount;
189 private int redirectCount;
191 private int maxRedirects;
193 private HttpHost virtualHost;
195 @Deprecated
196 public DefaultRequestDirector(
197 final HttpRequestExecutor requestExec,
198 final ClientConnectionManager conman,
199 final ConnectionReuseStrategy reustrat,
200 final ConnectionKeepAliveStrategy kastrat,
201 final HttpRoutePlanner rouplan,
202 final HttpProcessor httpProcessor,
203 final HttpRequestRetryHandler retryHandler,
204 final ch.boye.httpclientandroidlib.client.RedirectHandler redirectHandler,
205 final AuthenticationHandler targetAuthHandler,
206 final AuthenticationHandler proxyAuthHandler,
207 final UserTokenHandler userTokenHandler,
208 final HttpParams params) {
209 this(new HttpClientAndroidLog(DefaultRequestDirector.class),
210 requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
211 new DefaultRedirectStrategyAdaptor(redirectHandler),
212 targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
213 }
216 /**
217 * @since 4.1
218 */
219 public DefaultRequestDirector(
220 final HttpClientAndroidLog log,
221 final HttpRequestExecutor requestExec,
222 final ClientConnectionManager conman,
223 final ConnectionReuseStrategy reustrat,
224 final ConnectionKeepAliveStrategy kastrat,
225 final HttpRoutePlanner rouplan,
226 final HttpProcessor httpProcessor,
227 final HttpRequestRetryHandler retryHandler,
228 final RedirectStrategy redirectStrategy,
229 final AuthenticationHandler targetAuthHandler,
230 final AuthenticationHandler proxyAuthHandler,
231 final UserTokenHandler userTokenHandler,
232 final HttpParams params) {
234 if (log == null) {
235 throw new IllegalArgumentException
236 ("Log may not be null.");
237 }
238 if (requestExec == null) {
239 throw new IllegalArgumentException
240 ("Request executor may not be null.");
241 }
242 if (conman == null) {
243 throw new IllegalArgumentException
244 ("Client connection manager may not be null.");
245 }
246 if (reustrat == null) {
247 throw new IllegalArgumentException
248 ("Connection reuse strategy may not be null.");
249 }
250 if (kastrat == null) {
251 throw new IllegalArgumentException
252 ("Connection keep alive strategy may not be null.");
253 }
254 if (rouplan == null) {
255 throw new IllegalArgumentException
256 ("Route planner may not be null.");
257 }
258 if (httpProcessor == null) {
259 throw new IllegalArgumentException
260 ("HTTP protocol processor may not be null.");
261 }
262 if (retryHandler == null) {
263 throw new IllegalArgumentException
264 ("HTTP request retry handler may not be null.");
265 }
266 if (redirectStrategy == null) {
267 throw new IllegalArgumentException
268 ("Redirect strategy may not be null.");
269 }
270 if (targetAuthHandler == null) {
271 throw new IllegalArgumentException
272 ("Target authentication handler may not be null.");
273 }
274 if (proxyAuthHandler == null) {
275 throw new IllegalArgumentException
276 ("Proxy authentication handler may not be null.");
277 }
278 if (userTokenHandler == null) {
279 throw new IllegalArgumentException
280 ("User token handler may not be null.");
281 }
282 if (params == null) {
283 throw new IllegalArgumentException
284 ("HTTP parameters may not be null");
285 }
286 this.log = log;
287 this.requestExec = requestExec;
288 this.connManager = conman;
289 this.reuseStrategy = reustrat;
290 this.keepAliveStrategy = kastrat;
291 this.routePlanner = rouplan;
292 this.httpProcessor = httpProcessor;
293 this.retryHandler = retryHandler;
294 this.redirectStrategy = redirectStrategy;
295 this.targetAuthHandler = targetAuthHandler;
296 this.proxyAuthHandler = proxyAuthHandler;
297 this.userTokenHandler = userTokenHandler;
298 this.params = params;
300 this.managedConn = null;
302 this.execCount = 0;
303 this.redirectCount = 0;
304 this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
305 this.targetAuthState = new AuthState();
306 this.proxyAuthState = new AuthState();
307 } // constructor
310 private RequestWrapper wrapRequest(
311 final HttpRequest request) throws ProtocolException {
312 if (request instanceof HttpEntityEnclosingRequest) {
313 return new EntityEnclosingRequestWrapper(
314 (HttpEntityEnclosingRequest) request);
315 } else {
316 return new RequestWrapper(
317 request);
318 }
319 }
322 protected void rewriteRequestURI(
323 final RequestWrapper request,
324 final HttpRoute route) throws ProtocolException {
325 try {
327 URI uri = request.getURI();
328 if (route.getProxyHost() != null && !route.isTunnelled()) {
329 // Make sure the request URI is absolute
330 if (!uri.isAbsolute()) {
331 HttpHost target = route.getTargetHost();
332 uri = URIUtils.rewriteURI(uri, target);
333 request.setURI(uri);
334 }
335 } else {
336 // Make sure the request URI is relative
337 if (uri.isAbsolute()) {
338 uri = URIUtils.rewriteURI(uri, null);
339 request.setURI(uri);
340 }
341 }
343 } catch (URISyntaxException ex) {
344 throw new ProtocolException("Invalid URI: " +
345 request.getRequestLine().getUri(), ex);
346 }
347 }
350 // non-javadoc, see interface ClientRequestDirector
351 public HttpResponse execute(HttpHost target, HttpRequest request,
352 HttpContext context)
353 throws HttpException, IOException {
355 HttpRequest orig = request;
356 RequestWrapper origWrapper = wrapRequest(orig);
357 origWrapper.setParams(params);
358 HttpRoute origRoute = determineRoute(target, origWrapper, context);
360 virtualHost = (HttpHost) orig.getParams().getParameter(
361 ClientPNames.VIRTUAL_HOST);
363 // HTTPCLIENT-1092 - add the port if necessary
364 if (virtualHost != null && virtualHost.getPort() == -1)
365 {
366 int port = target.getPort();
367 if (port != -1){
368 virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
369 }
370 }
372 RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
374 boolean reuse = false;
375 boolean done = false;
376 try {
377 HttpResponse response = null;
378 while (!done) {
379 // In this loop, the RoutedRequest may be replaced by a
380 // followup request and route. The request and route passed
381 // in the method arguments will be replaced. The original
382 // request is still available in 'orig'.
384 RequestWrapper wrapper = roureq.getRequest();
385 HttpRoute route = roureq.getRoute();
386 response = null;
388 // See if we have a user token bound to the execution context
389 Object userToken = context.getAttribute(ClientContext.USER_TOKEN);
391 // Allocate connection if needed
392 if (managedConn == null) {
393 ClientConnectionRequest connRequest = connManager.requestConnection(
394 route, userToken);
395 if (orig instanceof AbortableHttpRequest) {
396 ((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
397 }
399 long timeout = ConnManagerParams.getTimeout(params);
400 try {
401 managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
402 } catch(InterruptedException interrupted) {
403 InterruptedIOException iox = new InterruptedIOException();
404 iox.initCause(interrupted);
405 throw iox;
406 }
408 if (HttpConnectionParams.isStaleCheckingEnabled(params)) {
409 // validate connection
410 if (managedConn.isOpen()) {
411 this.log.debug("Stale connection check");
412 if (managedConn.isStale()) {
413 this.log.debug("Stale connection detected");
414 managedConn.close();
415 }
416 }
417 }
418 }
420 if (orig instanceof AbortableHttpRequest) {
421 ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn);
422 }
424 try {
425 tryConnect(roureq, context);
426 } catch (TunnelRefusedException ex) {
427 if (this.log.isDebugEnabled()) {
428 this.log.debug(ex.getMessage());
429 }
430 response = ex.getResponse();
431 break;
432 }
434 // Reset headers on the request wrapper
435 wrapper.resetHeaders();
437 // Re-write request URI if needed
438 rewriteRequestURI(wrapper, route);
440 // Use virtual host if set
441 target = virtualHost;
443 if (target == null) {
444 target = route.getTargetHost();
445 }
447 HttpHost proxy = route.getProxyHost();
449 // Populate the execution context
450 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
451 target);
452 context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
453 proxy);
454 context.setAttribute(ExecutionContext.HTTP_CONNECTION,
455 managedConn);
456 context.setAttribute(ClientContext.TARGET_AUTH_STATE,
457 targetAuthState);
458 context.setAttribute(ClientContext.PROXY_AUTH_STATE,
459 proxyAuthState);
461 // Run request protocol interceptors
462 requestExec.preProcess(wrapper, httpProcessor, context);
464 response = tryExecute(roureq, context);
465 if (response == null) {
466 // Need to start over
467 continue;
468 }
470 // Run response protocol interceptors
471 response.setParams(params);
472 requestExec.postProcess(response, httpProcessor, context);
475 // The connection is in or can be brought to a re-usable state.
476 reuse = reuseStrategy.keepAlive(response, context);
477 if (reuse) {
478 // Set the idle duration of this connection
479 long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
480 if (this.log.isDebugEnabled()) {
481 String s;
482 if (duration > 0) {
483 s = "for " + duration + " " + TimeUnit.MILLISECONDS;
484 } else {
485 s = "indefinitely";
486 }
487 this.log.debug("Connection can be kept alive " + s);
488 }
489 managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
490 }
492 RoutedRequest followup = handleResponse(roureq, response, context);
493 if (followup == null) {
494 done = true;
495 } else {
496 if (reuse) {
497 // Make sure the response body is fully consumed, if present
498 HttpEntity entity = response.getEntity();
499 EntityUtils.consume(entity);
500 // entity consumed above is not an auto-release entity,
501 // need to mark the connection re-usable explicitly
502 managedConn.markReusable();
503 } else {
504 managedConn.close();
505 invalidateAuthIfSuccessful(this.proxyAuthState);
506 invalidateAuthIfSuccessful(this.targetAuthState);
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 }
515 if (managedConn != null && userToken == null) {
516 userToken = userTokenHandler.getUserToken(context);
517 context.setAttribute(ClientContext.USER_TOKEN, userToken);
518 if (userToken != null) {
519 managedConn.setState(userToken);
520 }
521 }
523 } // while not done
526 // check for entity, release connection if possible
527 if ((response == null) || (response.getEntity() == null) ||
528 !response.getEntity().isStreaming()) {
529 // connection not needed and (assumed to be) in re-usable state
530 if (reuse)
531 managedConn.markReusable();
532 releaseConnection();
533 } else {
534 // install an auto-release entity
535 HttpEntity entity = response.getEntity();
536 entity = new BasicManagedEntity(entity, managedConn, reuse);
537 response.setEntity(entity);
538 }
540 return response;
542 } catch (ConnectionShutdownException ex) {
543 InterruptedIOException ioex = new InterruptedIOException(
544 "Connection has been shut down");
545 ioex.initCause(ex);
546 throw ioex;
547 } catch (HttpException ex) {
548 abortConnection();
549 throw ex;
550 } catch (IOException ex) {
551 abortConnection();
552 throw ex;
553 } catch (RuntimeException ex) {
554 abortConnection();
555 throw ex;
556 }
557 } // execute
559 /**
560 * Establish connection either directly or through a tunnel and retry in case of
561 * a recoverable I/O failure
562 */
563 private void tryConnect(
564 final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
565 HttpRoute route = req.getRoute();
567 int connectCount = 0;
568 for (;;) {
569 // Increment connect count
570 connectCount++;
571 try {
572 if (!managedConn.isOpen()) {
573 managedConn.open(route, context, params);
574 } else {
575 managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params));
576 }
577 establishRoute(route, context);
578 break;
579 } catch (IOException ex) {
580 try {
581 managedConn.close();
582 } catch (IOException ignore) {
583 }
584 if (retryHandler.retryRequest(ex, connectCount, context)) {
585 if (this.log.isInfoEnabled()) {
586 this.log.info("I/O exception ("+ ex.getClass().getName() +
587 ") caught when connecting to the target host: "
588 + ex.getMessage());
589 }
590 if (this.log.isDebugEnabled()) {
591 this.log.debug(ex.getMessage(), ex);
592 }
593 this.log.info("Retrying connect");
594 } else {
595 throw ex;
596 }
597 }
598 }
599 }
601 /**
602 * Execute request and retry in case of a recoverable I/O failure
603 */
604 private HttpResponse tryExecute(
605 final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
606 RequestWrapper wrapper = req.getRequest();
607 HttpRoute route = req.getRoute();
608 HttpResponse response = null;
610 Exception retryReason = null;
611 for (;;) {
612 // Increment total exec count (with redirects)
613 execCount++;
614 // Increment exec count for this particular request
615 wrapper.incrementExecCount();
616 if (!wrapper.isRepeatable()) {
617 this.log.debug("Cannot retry non-repeatable request");
618 if (retryReason != null) {
619 throw new NonRepeatableRequestException("Cannot retry request " +
620 "with a non-repeatable request entity. The cause lists the " +
621 "reason the original request failed.", retryReason);
622 } else {
623 throw new NonRepeatableRequestException("Cannot retry request " +
624 "with a non-repeatable request entity.");
625 }
626 }
628 try {
629 if (!managedConn.isOpen()) {
630 // If we have a direct route to the target host
631 // just re-open connection and re-try the request
632 if (!route.isTunnelled()) {
633 this.log.debug("Reopening the direct connection.");
634 managedConn.open(route, context, params);
635 } else {
636 // otherwise give up
637 this.log.debug("Proxied connection. Need to start over.");
638 break;
639 }
640 }
642 if (this.log.isDebugEnabled()) {
643 this.log.debug("Attempt " + execCount + " to execute request");
644 }
645 response = requestExec.execute(wrapper, managedConn, context);
646 break;
648 } catch (IOException ex) {
649 this.log.debug("Closing the connection.");
650 try {
651 managedConn.close();
652 } catch (IOException ignore) {
653 }
654 if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) {
655 if (this.log.isInfoEnabled()) {
656 this.log.info("I/O exception ("+ ex.getClass().getName() +
657 ") caught when processing request: "
658 + ex.getMessage());
659 }
660 if (this.log.isDebugEnabled()) {
661 this.log.debug(ex.getMessage(), ex);
662 }
663 this.log.info("Retrying request");
664 retryReason = ex;
665 } else {
666 throw ex;
667 }
668 }
669 }
670 return response;
671 }
673 /**
674 * Returns the connection back to the connection manager
675 * and prepares for retrieving a new connection during
676 * the next request.
677 */
678 protected void releaseConnection() {
679 // Release the connection through the ManagedConnection instead of the
680 // ConnectionManager directly. This lets the connection control how
681 // it is released.
682 try {
683 managedConn.releaseConnection();
684 } catch(IOException ignored) {
685 this.log.debug("IOException releasing connection", ignored);
686 }
687 managedConn = null;
688 }
690 /**
691 * Determines the route for a request.
692 * Called by {@link #execute}
693 * to determine the route for either the original or a followup request.
694 *
695 * @param target the target host for the request.
696 * Implementations may accept <code>null</code>
697 * if they can still determine a route, for example
698 * to a default target or by inspecting the request.
699 * @param request the request to execute
700 * @param context the context to use for the execution,
701 * never <code>null</code>
702 *
703 * @return the route the request should take
704 *
705 * @throws HttpException in case of a problem
706 */
707 protected HttpRoute determineRoute(HttpHost target,
708 HttpRequest request,
709 HttpContext context)
710 throws HttpException {
712 if (target == null) {
713 target = (HttpHost) request.getParams().getParameter(
714 ClientPNames.DEFAULT_HOST);
715 }
716 if (target == null) {
717 throw new IllegalStateException
718 ("Target host must not be null, or set in parameters.");
719 }
721 return this.routePlanner.determineRoute(target, request, context);
722 }
725 /**
726 * Establishes the target route.
727 *
728 * @param route the route to establish
729 * @param context the context for the request execution
730 *
731 * @throws HttpException in case of a problem
732 * @throws IOException in case of an IO problem
733 */
734 protected void establishRoute(HttpRoute route, HttpContext context)
735 throws HttpException, IOException {
737 HttpRouteDirector rowdy = new BasicRouteDirector();
738 int step;
739 do {
740 HttpRoute fact = managedConn.getRoute();
741 step = rowdy.nextStep(route, fact);
743 switch (step) {
745 case HttpRouteDirector.CONNECT_TARGET:
746 case HttpRouteDirector.CONNECT_PROXY:
747 managedConn.open(route, context, this.params);
748 break;
750 case HttpRouteDirector.TUNNEL_TARGET: {
751 boolean secure = createTunnelToTarget(route, context);
752 this.log.debug("Tunnel to target created.");
753 managedConn.tunnelTarget(secure, this.params);
754 } break;
756 case HttpRouteDirector.TUNNEL_PROXY: {
757 // The most simple example for this case is a proxy chain
758 // of two proxies, where P1 must be tunnelled to P2.
759 // route: Source -> P1 -> P2 -> Target (3 hops)
760 // fact: Source -> P1 -> Target (2 hops)
761 final int hop = fact.getHopCount()-1; // the hop to establish
762 boolean secure = createTunnelToProxy(route, hop, context);
763 this.log.debug("Tunnel to proxy created.");
764 managedConn.tunnelProxy(route.getHopTarget(hop),
765 secure, this.params);
766 } break;
769 case HttpRouteDirector.LAYER_PROTOCOL:
770 managedConn.layerProtocol(context, this.params);
771 break;
773 case HttpRouteDirector.UNREACHABLE:
774 throw new HttpException("Unable to establish route: " +
775 "planned = " + route + "; current = " + fact);
776 case HttpRouteDirector.COMPLETE:
777 // do nothing
778 break;
779 default:
780 throw new IllegalStateException("Unknown step indicator "
781 + step + " from RouteDirector.");
782 }
784 } while (step > HttpRouteDirector.COMPLETE);
786 } // establishConnection
789 /**
790 * Creates a tunnel to the target server.
791 * The connection must be established to the (last) proxy.
792 * A CONNECT request for tunnelling through the proxy will
793 * be created and sent, the response received and checked.
794 * This method does <i>not</i> update the connection with
795 * information about the tunnel, that is left to the caller.
796 *
797 * @param route the route to establish
798 * @param context the context for request execution
799 *
800 * @return <code>true</code> if the tunnelled route is secure,
801 * <code>false</code> otherwise.
802 * The implementation here always returns <code>false</code>,
803 * but derived classes may override.
804 *
805 * @throws HttpException in case of a problem
806 * @throws IOException in case of an IO problem
807 */
808 protected boolean createTunnelToTarget(HttpRoute route,
809 HttpContext context)
810 throws HttpException, IOException {
812 HttpHost proxy = route.getProxyHost();
813 HttpHost target = route.getTargetHost();
814 HttpResponse response = null;
816 boolean done = false;
817 while (!done) {
819 done = true;
821 if (!this.managedConn.isOpen()) {
822 this.managedConn.open(route, context, this.params);
823 }
825 HttpRequest connect = createConnectRequest(route, context);
826 connect.setParams(this.params);
828 // Populate the execution context
829 context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
830 target);
831 context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
832 proxy);
833 context.setAttribute(ExecutionContext.HTTP_CONNECTION,
834 managedConn);
835 context.setAttribute(ClientContext.TARGET_AUTH_STATE,
836 targetAuthState);
837 context.setAttribute(ClientContext.PROXY_AUTH_STATE,
838 proxyAuthState);
839 context.setAttribute(ExecutionContext.HTTP_REQUEST,
840 connect);
842 this.requestExec.preProcess(connect, this.httpProcessor, context);
844 response = this.requestExec.execute(connect, this.managedConn, context);
846 response.setParams(this.params);
847 this.requestExec.postProcess(response, this.httpProcessor, context);
849 int status = response.getStatusLine().getStatusCode();
850 if (status < 200) {
851 throw new HttpException("Unexpected response to CONNECT request: " +
852 response.getStatusLine());
853 }
855 CredentialsProvider credsProvider = (CredentialsProvider)
856 context.getAttribute(ClientContext.CREDS_PROVIDER);
858 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
859 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
861 this.log.debug("Proxy requested authentication");
862 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
863 response, context);
864 try {
865 processChallenges(
866 challenges, this.proxyAuthState, this.proxyAuthHandler,
867 response, context);
868 } catch (AuthenticationException ex) {
869 if (this.log.isWarnEnabled()) {
870 this.log.warn("Authentication error: " + ex.getMessage());
871 break;
872 }
873 }
874 updateAuthState(this.proxyAuthState, proxy, credsProvider);
876 if (this.proxyAuthState.getCredentials() != null) {
877 done = false;
879 // Retry request
880 if (this.reuseStrategy.keepAlive(response, context)) {
881 this.log.debug("Connection kept alive");
882 // Consume response content
883 HttpEntity entity = response.getEntity();
884 EntityUtils.consume(entity);
885 } else {
886 this.managedConn.close();
887 }
889 }
891 } else {
892 // Reset proxy auth scope
893 this.proxyAuthState.setAuthScope(null);
894 }
895 }
896 }
898 int status = response.getStatusLine().getStatusCode(); // can't be null
900 if (status > 299) {
902 // Buffer response content
903 HttpEntity entity = response.getEntity();
904 if (entity != null) {
905 response.setEntity(new BufferedHttpEntity(entity));
906 }
908 this.managedConn.close();
909 throw new TunnelRefusedException("CONNECT refused by proxy: " +
910 response.getStatusLine(), response);
911 }
913 this.managedConn.markReusable();
915 // How to decide on security of the tunnelled connection?
916 // The socket factory knows only about the segment to the proxy.
917 // Even if that is secure, the hop to the target may be insecure.
918 // Leave it to derived classes, consider insecure by default here.
919 return false;
921 } // createTunnelToTarget
925 /**
926 * Creates a tunnel to an intermediate proxy.
927 * This method is <i>not</i> implemented in this class.
928 * It just throws an exception here.
929 *
930 * @param route the route to establish
931 * @param hop the hop in the route to establish now.
932 * <code>route.getHopTarget(hop)</code>
933 * will return the proxy to tunnel to.
934 * @param context the context for request execution
935 *
936 * @return <code>true</code> if the partially tunnelled connection
937 * is secure, <code>false</code> otherwise.
938 *
939 * @throws HttpException in case of a problem
940 * @throws IOException in case of an IO problem
941 */
942 protected boolean createTunnelToProxy(HttpRoute route, int hop,
943 HttpContext context)
944 throws HttpException, IOException {
946 // Have a look at createTunnelToTarget and replicate the parts
947 // you need in a custom derived class. If your proxies don't require
948 // authentication, it is not too hard. But for the stock version of
949 // HttpClient, we cannot make such simplifying assumptions and would
950 // have to include proxy authentication code. The HttpComponents team
951 // is currently not in a position to support rarely used code of this
952 // complexity. Feel free to submit patches that refactor the code in
953 // createTunnelToTarget to facilitate re-use for proxy tunnelling.
955 throw new HttpException("Proxy chains are not supported.");
956 }
960 /**
961 * Creates the CONNECT request for tunnelling.
962 * Called by {@link #createTunnelToTarget createTunnelToTarget}.
963 *
964 * @param route the route to establish
965 * @param context the context for request execution
966 *
967 * @return the CONNECT request for tunnelling
968 */
969 protected HttpRequest createConnectRequest(HttpRoute route,
970 HttpContext context) {
971 // see RFC 2817, section 5.2 and
972 // INTERNET-DRAFT: Tunneling TCP based protocols through
973 // Web proxy servers
975 HttpHost target = route.getTargetHost();
977 String host = target.getHostName();
978 int port = target.getPort();
979 if (port < 0) {
980 Scheme scheme = connManager.getSchemeRegistry().
981 getScheme(target.getSchemeName());
982 port = scheme.getDefaultPort();
983 }
985 StringBuilder buffer = new StringBuilder(host.length() + 6);
986 buffer.append(host);
987 buffer.append(':');
988 buffer.append(Integer.toString(port));
990 String authority = buffer.toString();
991 ProtocolVersion ver = HttpProtocolParams.getVersion(params);
992 HttpRequest req = new BasicHttpRequest
993 ("CONNECT", authority, ver);
995 return req;
996 }
999 /**
1000 * Analyzes a response to check need for a followup.
1001 *
1002 * @param roureq the request and route.
1003 * @param response the response to analayze
1004 * @param context the context used for the current request execution
1005 *
1006 * @return the followup request and route if there is a followup, or
1007 * <code>null</code> if the response should be returned as is
1008 *
1009 * @throws HttpException in case of a problem
1010 * @throws IOException in case of an IO problem
1011 */
1012 protected RoutedRequest handleResponse(RoutedRequest roureq,
1013 HttpResponse response,
1014 HttpContext context)
1015 throws HttpException, IOException {
1017 HttpRoute route = roureq.getRoute();
1018 RequestWrapper request = roureq.getRequest();
1020 HttpParams params = request.getParams();
1021 if (HttpClientParams.isRedirecting(params) &&
1022 this.redirectStrategy.isRedirected(request, response, context)) {
1024 if (redirectCount >= maxRedirects) {
1025 throw new RedirectException("Maximum redirects ("
1026 + maxRedirects + ") exceeded");
1027 }
1028 redirectCount++;
1030 // Virtual host cannot be used any longer
1031 virtualHost = null;
1033 HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context);
1034 HttpRequest orig = request.getOriginal();
1035 redirect.setHeaders(orig.getAllHeaders());
1037 URI uri = redirect.getURI();
1038 if (uri.getHost() == null) {
1039 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
1040 }
1042 HttpHost newTarget = new HttpHost(
1043 uri.getHost(),
1044 uri.getPort(),
1045 uri.getScheme());
1047 // Unset auth scope
1048 targetAuthState.setAuthScope(null);
1049 proxyAuthState.setAuthScope(null);
1051 // Invalidate auth states if redirecting to another host
1052 if (!route.getTargetHost().equals(newTarget)) {
1053 targetAuthState.invalidate();
1054 AuthScheme authScheme = proxyAuthState.getAuthScheme();
1055 if (authScheme != null && authScheme.isConnectionBased()) {
1056 proxyAuthState.invalidate();
1057 }
1058 }
1060 RequestWrapper wrapper = wrapRequest(redirect);
1061 wrapper.setParams(params);
1063 HttpRoute newRoute = determineRoute(newTarget, wrapper, context);
1064 RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute);
1066 if (this.log.isDebugEnabled()) {
1067 this.log.debug("Redirecting to '" + uri + "' via " + newRoute);
1068 }
1070 return newRequest;
1071 }
1073 CredentialsProvider credsProvider = (CredentialsProvider)
1074 context.getAttribute(ClientContext.CREDS_PROVIDER);
1076 if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
1078 if (this.targetAuthHandler.isAuthenticationRequested(response, context)) {
1080 HttpHost target = (HttpHost)
1081 context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
1082 if (target == null) {
1083 target = route.getTargetHost();
1084 }
1086 this.log.debug("Target requested authentication");
1087 Map<String, Header> challenges = this.targetAuthHandler.getChallenges(
1088 response, context);
1089 try {
1090 processChallenges(challenges,
1091 this.targetAuthState, this.targetAuthHandler,
1092 response, context);
1093 } catch (AuthenticationException ex) {
1094 if (this.log.isWarnEnabled()) {
1095 this.log.warn("Authentication error: " + ex.getMessage());
1096 return null;
1097 }
1098 }
1099 updateAuthState(this.targetAuthState, target, credsProvider);
1101 if (this.targetAuthState.getCredentials() != null) {
1102 // Re-try the same request via the same route
1103 return roureq;
1104 } else {
1105 return null;
1106 }
1107 } else {
1108 // Reset target auth scope
1109 this.targetAuthState.setAuthScope(null);
1110 }
1112 if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
1114 HttpHost proxy = route.getProxyHost();
1116 this.log.debug("Proxy requested authentication");
1117 Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
1118 response, context);
1119 try {
1120 processChallenges(challenges,
1121 this.proxyAuthState, this.proxyAuthHandler,
1122 response, context);
1123 } catch (AuthenticationException ex) {
1124 if (this.log.isWarnEnabled()) {
1125 this.log.warn("Authentication error: " + ex.getMessage());
1126 return null;
1127 }
1128 }
1129 updateAuthState(this.proxyAuthState, proxy, credsProvider);
1131 if (this.proxyAuthState.getCredentials() != null) {
1132 // Re-try the same request via the same route
1133 return roureq;
1134 } else {
1135 return null;
1136 }
1137 } else {
1138 // Reset proxy auth scope
1139 this.proxyAuthState.setAuthScope(null);
1140 }
1141 }
1142 return null;
1143 } // handleResponse
1146 /**
1147 * Shuts down the connection.
1148 * This method is called from a <code>catch</code> block in
1149 * {@link #execute execute} during exception handling.
1150 */
1151 private void abortConnection() {
1152 ManagedClientConnection mcc = managedConn;
1153 if (mcc != null) {
1154 // we got here as the result of an exception
1155 // no response will be returned, release the connection
1156 managedConn = null;
1157 try {
1158 mcc.abortConnection();
1159 } catch (IOException ex) {
1160 if (this.log.isDebugEnabled()) {
1161 this.log.debug(ex.getMessage(), ex);
1162 }
1163 }
1164 // ensure the connection manager properly releases this connection
1165 try {
1166 mcc.releaseConnection();
1167 } catch(IOException ignored) {
1168 this.log.debug("Error releasing connection", ignored);
1169 }
1170 }
1171 } // abortConnection
1174 private void processChallenges(
1175 final Map<String, Header> challenges,
1176 final AuthState authState,
1177 final AuthenticationHandler authHandler,
1178 final HttpResponse response,
1179 final HttpContext context)
1180 throws MalformedChallengeException, AuthenticationException {
1182 AuthScheme authScheme = authState.getAuthScheme();
1183 if (authScheme == null) {
1184 // Authentication not attempted before
1185 authScheme = authHandler.selectScheme(challenges, response, context);
1186 authState.setAuthScheme(authScheme);
1187 }
1188 String id = authScheme.getSchemeName();
1190 Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
1191 if (challenge == null) {
1192 throw new AuthenticationException(id +
1193 " authorization challenge expected, but not found");
1194 }
1195 authScheme.processChallenge(challenge);
1196 this.log.debug("Authorization challenge processed");
1197 }
1200 private void updateAuthState(
1201 final AuthState authState,
1202 final HttpHost host,
1203 final CredentialsProvider credsProvider) {
1205 if (!authState.isValid()) {
1206 return;
1207 }
1209 String hostname = host.getHostName();
1210 int port = host.getPort();
1211 if (port < 0) {
1212 Scheme scheme = connManager.getSchemeRegistry().getScheme(host);
1213 port = scheme.getDefaultPort();
1214 }
1216 AuthScheme authScheme = authState.getAuthScheme();
1217 AuthScope authScope = new AuthScope(
1218 hostname,
1219 port,
1220 authScheme.getRealm(),
1221 authScheme.getSchemeName());
1223 if (this.log.isDebugEnabled()) {
1224 this.log.debug("Authentication scope: " + authScope);
1225 }
1226 Credentials creds = authState.getCredentials();
1227 if (creds == null) {
1228 creds = credsProvider.getCredentials(authScope);
1229 if (this.log.isDebugEnabled()) {
1230 if (creds != null) {
1231 this.log.debug("Found credentials");
1232 } else {
1233 this.log.debug("Credentials not found");
1234 }
1235 }
1236 } else {
1237 if (authScheme.isComplete()) {
1238 this.log.debug("Authentication failed");
1239 creds = null;
1240 }
1241 }
1242 authState.setAuthScope(authScope);
1243 authState.setCredentials(creds);
1244 }
1246 private void invalidateAuthIfSuccessful(final AuthState authState) {
1247 AuthScheme authscheme = authState.getAuthScheme();
1248 if (authscheme != null
1249 && authscheme.isConnectionBased()
1250 && authscheme.isComplete()
1251 && authState.getCredentials() != null) {
1252 authState.invalidate();
1253 }
1254 }
1256 } // class DefaultClientRequestDirector