View Javadoc
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   */
27  package org.apache.http.impl.nio.client;
28  
29  import java.io.IOException;
30  import java.net.URI;
31  import java.net.URISyntaxException;
32  import java.nio.ByteBuffer;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.atomic.AtomicLong;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.http.ConnectionClosedException;
38  import org.apache.http.ConnectionReuseStrategy;
39  import org.apache.http.HttpEntityEnclosingRequest;
40  import org.apache.http.HttpException;
41  import org.apache.http.HttpHost;
42  import org.apache.http.HttpRequest;
43  import org.apache.http.HttpResponse;
44  import org.apache.http.HttpStatus;
45  import org.apache.http.ProtocolException;
46  import org.apache.http.ProtocolVersion;
47  import org.apache.http.auth.AuthProtocolState;
48  import org.apache.http.auth.AuthScheme;
49  import org.apache.http.auth.AuthState;
50  import org.apache.http.auth.UsernamePasswordCredentials;
51  import org.apache.http.client.AuthenticationStrategy;
52  import org.apache.http.client.CredentialsProvider;
53  import org.apache.http.client.NonRepeatableRequestException;
54  import org.apache.http.client.RedirectException;
55  import org.apache.http.client.RedirectStrategy;
56  import org.apache.http.client.UserTokenHandler;
57  import org.apache.http.client.config.RequestConfig;
58  import org.apache.http.client.methods.AbortableHttpRequest;
59  import org.apache.http.client.methods.HttpUriRequest;
60  import org.apache.http.client.params.ClientPNames;
61  import org.apache.http.client.params.HttpClientParams;
62  import org.apache.http.client.protocol.ClientContext;
63  import org.apache.http.client.utils.URIUtils;
64  import org.apache.http.concurrent.FutureCallback;
65  import org.apache.http.conn.ConnectionKeepAliveStrategy;
66  import org.apache.http.conn.ConnectionReleaseTrigger;
67  import org.apache.http.conn.routing.BasicRouteDirector;
68  import org.apache.http.conn.routing.HttpRoute;
69  import org.apache.http.conn.routing.HttpRouteDirector;
70  import org.apache.http.conn.routing.HttpRoutePlanner;
71  import org.apache.http.impl.auth.BasicScheme;
72  import org.apache.http.impl.client.ClientParamsStack;
73  import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
74  import org.apache.http.impl.client.HttpAuthenticator;
75  import org.apache.http.impl.client.RequestWrapper;
76  import org.apache.http.impl.client.RoutedRequest;
77  import org.apache.http.message.BasicHttpRequest;
78  import org.apache.http.nio.ContentDecoder;
79  import org.apache.http.nio.ContentEncoder;
80  import org.apache.http.nio.IOControl;
81  import org.apache.http.nio.conn.ClientAsyncConnectionManager;
82  import org.apache.http.nio.conn.ManagedClientAsyncConnection;
83  import org.apache.http.nio.conn.scheme.AsyncScheme;
84  import org.apache.http.nio.conn.scheme.AsyncSchemeRegistry;
85  import org.apache.http.nio.protocol.HttpAsyncRequestExecutionHandler;
86  import org.apache.http.nio.protocol.HttpAsyncRequestExecutor;
87  import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
88  import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
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.HttpContext;
94  import org.apache.http.protocol.HttpProcessor;
95  
96  @Deprecated
97  class DefaultAsyncRequestDirector<T> implements HttpAsyncRequestExecutionHandler<T> {
98  
99      private static final AtomicLong COUNTER = new AtomicLong(1);
100 
101     private final Log log;
102 
103     private final HttpAsyncRequestProducer requestProducer;
104     private final HttpAsyncResponseConsumer<T> responseConsumer;
105     private final HttpContext localContext;
106     private final ResultCallback<T> resultCallback;
107     private final ClientAsyncConnectionManager connmgr;
108     private final HttpProcessor httppocessor;
109     private final HttpRoutePlanner routePlanner;
110     private final HttpRouteDirector routeDirector;
111     private final ConnectionReuseStrategy reuseStrategy;
112     private final ConnectionKeepAliveStrategy keepaliveStrategy;
113     private final RedirectStrategy redirectStrategy;
114     private final AuthenticationStrategy targetAuthStrategy;
115     private final AuthenticationStrategy proxyAuthStrategy;
116     private final UserTokenHandler userTokenHandler;
117     private final AuthState targetAuthState;
118     private final AuthState proxyAuthState;
119     private final HttpAuthenticator authenticator;
120     private final HttpParams clientParams;
121     private final long id;
122 
123     private volatile boolean closed;
124     private volatile InternalFutureCallback connRequestCallback;
125     private volatile ManagedClientAsyncConnection managedConn;
126 
127     private RoutedRequest mainRequest;
128     private RoutedRequest followup;
129     private HttpResponse finalResponse;
130 
131     private ClientParamsStack params;
132     private RequestWrapper currentRequest;
133     private HttpResponse currentResponse;
134     private boolean routeEstablished;
135     private int redirectCount;
136     private ByteBuffer tmpbuf;
137     private boolean requestContentProduced;
138     private boolean requestSent;
139     private int execCount;
140 
141     public DefaultAsyncRequestDirector(
142             final Log log,
143             final HttpAsyncRequestProducer requestProducer,
144             final HttpAsyncResponseConsumer<T> responseConsumer,
145             final HttpContext localContext,
146             final ResultCallback<T> callback,
147             final ClientAsyncConnectionManager connmgr,
148             final HttpProcessor httppocessor,
149             final HttpRoutePlanner routePlanner,
150             final ConnectionReuseStrategy reuseStrategy,
151             final ConnectionKeepAliveStrategy keepaliveStrategy,
152             final RedirectStrategy redirectStrategy,
153             final AuthenticationStrategy targetAuthStrategy,
154             final AuthenticationStrategy proxyAuthStrategy,
155             final UserTokenHandler userTokenHandler,
156             final HttpParams clientParams) {
157         super();
158         this.log = log;
159         this.requestProducer = requestProducer;
160         this.responseConsumer = responseConsumer;
161         this.localContext = localContext;
162         this.resultCallback = callback;
163         this.connmgr = connmgr;
164         this.httppocessor = httppocessor;
165         this.routePlanner = routePlanner;
166         this.reuseStrategy = reuseStrategy;
167         this.keepaliveStrategy = keepaliveStrategy;
168         this.redirectStrategy = redirectStrategy;
169         this.routeDirector = new BasicRouteDirector();
170         this.targetAuthStrategy = targetAuthStrategy;
171         this.proxyAuthStrategy = proxyAuthStrategy;
172         this.userTokenHandler = userTokenHandler;
173         this.targetAuthState = new AuthState();
174         this.proxyAuthState = new AuthState();
175         this.authenticator     = new HttpAuthenticator(log);
176         this.clientParams = clientParams;
177         this.id = COUNTER.getAndIncrement();
178     }
179 
180     @Override
181     public void close() {
182         if (this.closed) {
183             return;
184         }
185         this.closed = true;
186         final ManagedClientAsyncConnection localConn = this.managedConn;
187         if (localConn != null) {
188             if (this.log.isDebugEnabled()) {
189                 this.log.debug("[exchange: " + this.id + "] aborting connection " + localConn);
190             }
191             try {
192                 localConn.abortConnection();
193             } catch (final IOException ioex) {
194                 this.log.debug("I/O error releasing connection", ioex);
195             }
196         }
197         try {
198             this.requestProducer.close();
199         } catch (final IOException ex) {
200             this.log.debug("I/O error closing request producer", ex);
201         }
202         try {
203             this.responseConsumer.close();
204         } catch (final IOException ex) {
205             this.log.debug("I/O error closing response consumer", ex);
206         }
207     }
208 
209     public synchronized void start() {
210         try {
211             if (this.log.isDebugEnabled()) {
212                 this.log.debug("[exchange: " + this.id + "] start execution");
213             }
214             this.localContext.setAttribute(ClientContext.TARGET_AUTH_STATE, this.targetAuthState);
215             this.localContext.setAttribute(ClientContext.PROXY_AUTH_STATE, this.proxyAuthState);
216 
217             final HttpHost target = this.requestProducer.getTarget();
218             final HttpRequest request = this.requestProducer.generateRequest();
219             if (request instanceof AbortableHttpRequest) {
220                 ((AbortableHttpRequest) request).setReleaseTrigger(new ConnectionReleaseTrigger() {
221 
222                     @Override
223                     public void releaseConnection() throws IOException {
224                     }
225 
226                     @Override
227                     public void abortConnection() throws IOException {
228                         cancel();
229                     }
230 
231                 });
232             }
233             this.params = new ClientParamsStack(null, this.clientParams, request.getParams(), null);
234             final RequestWrapper wrapper = wrapRequest(request);
235             wrapper.setParams(this.params);
236             final HttpRoute route = determineRoute(target, wrapper, this.localContext);
237             this.mainRequest = new RoutedRequest(wrapper, route);
238             final RequestConfig config = ParamConfig.getRequestConfig(params);
239             this.localContext.setAttribute(ClientContext.REQUEST_CONFIG, config);
240             this.requestContentProduced = false;
241             requestConnection();
242         } catch (final Exception ex) {
243             failed(ex);
244         }
245     }
246 
247     @Override
248     public HttpHost getTarget() {
249         return this.requestProducer.getTarget();
250     }
251 
252     @Override
253     public synchronized HttpRequest generateRequest() throws IOException, HttpException {
254         final HttpRoute route = this.mainRequest.getRoute();
255         if (!this.routeEstablished) {
256             int step;
257             do {
258                 final HttpRoute fact = this.managedConn.getRoute();
259                 step = this.routeDirector.nextStep(route, fact);
260                 switch (step) {
261                 case HttpRouteDirector.CONNECT_TARGET:
262                 case HttpRouteDirector.CONNECT_PROXY:
263                     break;
264                 case HttpRouteDirector.TUNNEL_TARGET:
265                     if (this.log.isDebugEnabled()) {
266                         this.log.debug("[exchange: " + this.id + "] Tunnel required");
267                     }
268                     final HttpRequest connect = createConnectRequest(route);
269                     this.currentRequest = wrapRequest(connect);
270                     this.currentRequest.setParams(this.params);
271                     break;
272                 case HttpRouteDirector.TUNNEL_PROXY:
273                     throw new HttpException("Proxy chains are not supported");
274                 case HttpRouteDirector.LAYER_PROTOCOL:
275                     managedConn.layerProtocol(this.localContext, this.params);
276                     break;
277                 case HttpRouteDirector.UNREACHABLE:
278                     throw new HttpException("Unable to establish route: " +
279                             "planned = " + route + "; current = " + fact);
280                 case HttpRouteDirector.COMPLETE:
281                     this.routeEstablished = true;
282                     break;
283                 default:
284                     throw new IllegalStateException("Unknown step indicator "
285                             + step + " from RouteDirector.");
286                 }
287             } while (step > HttpRouteDirector.COMPLETE && this.currentRequest == null);
288         }
289 
290         HttpHost target = (HttpHost) this.params.getParameter(ClientPNames.VIRTUAL_HOST);
291         if (target == null) {
292             target = route.getTargetHost();
293         }
294         final HttpHost proxy = route.getProxyHost();
295         this.localContext.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
296         this.localContext.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
297         this.localContext.setAttribute(ExecutionContext.HTTP_CONNECTION, this.managedConn);
298         this.localContext.setAttribute(ClientContext.ROUTE, route);
299 
300         if (this.currentRequest == null) {
301             this.currentRequest = this.mainRequest.getRequest();
302 
303             final String userinfo = this.currentRequest.getURI().getUserInfo();
304             if (userinfo != null) {
305                 this.targetAuthState.update(
306                         new BasicScheme(), new UsernamePasswordCredentials(userinfo));
307             }
308 
309             // Re-write request URI if needed
310             rewriteRequestURI(this.currentRequest, route);
311         }
312         // Reset headers on the request wrapper
313         this.currentRequest.resetHeaders();
314 
315         this.currentRequest.incrementExecCount();
316         if (this.currentRequest.getExecCount() > 1
317                 && !this.requestProducer.isRepeatable()
318                 && this.requestContentProduced) {
319             throw new NonRepeatableRequestException("Cannot retry request " +
320                 "with a non-repeatable request entity.");
321         }
322         this.execCount++;
323         if (this.log.isDebugEnabled()) {
324             this.log.debug("[exchange: " + this.id + "] Attempt " + this.execCount + " to execute request");
325         }
326         return this.currentRequest;
327     }
328 
329     @Override
330     public synchronized void produceContent(
331             final ContentEncoder encoder, final IOControl ioctrl) throws IOException {
332         if (this.log.isDebugEnabled()) {
333             this.log.debug("[exchange: " + this.id + "] produce content");
334         }
335         this.requestContentProduced = true;
336         this.requestProducer.produceContent(encoder, ioctrl);
337         if (encoder.isCompleted()) {
338             this.requestProducer.resetRequest();
339         }
340     }
341 
342     @Override
343     public void requestCompleted(final HttpContext context) {
344         if (this.log.isDebugEnabled()) {
345             this.log.debug("[exchange: " + this.id + "] Request completed");
346         }
347         this.requestSent = true;
348         this.requestProducer.requestCompleted(context);
349     }
350 
351     @Override
352     public boolean isRepeatable() {
353         return this.requestProducer.isRepeatable();
354     }
355 
356     @Override
357     public void resetRequest() throws IOException {
358         this.requestSent = false;
359         this.requestProducer.resetRequest();
360     }
361 
362     @Override
363     public synchronized void responseReceived(
364             final HttpResponse response) throws IOException, HttpException {
365         if (this.log.isDebugEnabled()) {
366             this.log.debug("[exchange: " + this.id + "] Response received " + response.getStatusLine());
367         }
368         this.currentResponse = response;
369         this.currentResponse.setParams(this.params);
370 
371         final int status = this.currentResponse.getStatusLine().getStatusCode();
372 
373         if (!this.routeEstablished) {
374             final String method = this.currentRequest.getMethod();
375             if (method.equalsIgnoreCase("CONNECT") && status == HttpStatus.SC_OK) {
376                 this.managedConn.tunnelTarget(this.params);
377             } else {
378                 this.followup = handleConnectResponse();
379                 if (this.followup == null) {
380                     this.finalResponse = response;
381                 }
382             }
383         } else {
384             this.followup = handleResponse();
385             if (this.followup == null) {
386                 this.finalResponse = response;
387             }
388 
389             Object userToken = this.localContext.getAttribute(ClientContext.USER_TOKEN);
390             if (managedConn != null) {
391                 if (userToken == null) {
392                     userToken = userTokenHandler.getUserToken(this.localContext);
393                     this.localContext.setAttribute(ClientContext.USER_TOKEN, userToken);
394                 }
395                 if (userToken != null) {
396                     managedConn.setState(userToken);
397                 }
398             }
399         }
400         if (this.finalResponse != null) {
401             this.responseConsumer.responseReceived(response);
402         }
403     }
404 
405     @Override
406     public synchronized void consumeContent(
407             final ContentDecoder decoder, final IOControl ioctrl) throws IOException {
408         if (this.log.isDebugEnabled()) {
409             this.log.debug("[exchange: " + this.id + "] Consume content");
410         }
411         if (this.finalResponse != null) {
412             this.responseConsumer.consumeContent(decoder, ioctrl);
413         } else {
414             if (this.tmpbuf == null) {
415                 this.tmpbuf = ByteBuffer.allocate(2048);
416             }
417             this.tmpbuf.clear();
418             decoder.read(this.tmpbuf);
419         }
420     }
421 
422     private void releaseConnection() {
423         if (this.managedConn != null) {
424             if (this.log.isDebugEnabled()) {
425                 this.log.debug("[exchange: " + this.id + "] releasing connection " + this.managedConn);
426             }
427             try {
428                 this.managedConn.getContext().removeAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER);
429                 this.managedConn.releaseConnection();
430             } catch (final IOException ioex) {
431                 this.log.debug("I/O error releasing connection", ioex);
432             }
433             this.managedConn = null;
434         }
435     }
436 
437     @Override
438     public synchronized void failed(final Exception ex) {
439         try {
440             if (!this.requestSent) {
441                 this.requestProducer.failed(ex);
442             }
443             this.responseConsumer.failed(ex);
444         } finally {
445             try {
446                 this.resultCallback.failed(ex, this);
447             } finally {
448                 close();
449             }
450         }
451     }
452 
453     @Override
454     public synchronized void responseCompleted(final HttpContext context) {
455         if (this.log.isDebugEnabled()) {
456             this.log.debug("[exchange: " + this.id + "] Response fully read");
457         }
458         try {
459             if (this.resultCallback.isDone()) {
460                 return;
461             }
462             if (this.managedConn.isOpen()) {
463                 final long duration = this.keepaliveStrategy.getKeepAliveDuration(
464                         this.currentResponse, this.localContext);
465                 if (this.log.isDebugEnabled()) {
466                     final String s;
467                     if (duration > 0) {
468                         s = "for " + duration + " " + TimeUnit.MILLISECONDS;
469                     } else {
470                         s = "indefinitely";
471                     }
472                     this.log.debug("[exchange: " + this.id + "] Connection can be kept alive " + s);
473                 }
474                 this.managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
475             } else {
476                 if (this.log.isDebugEnabled()) {
477                     this.log.debug("[exchange: " + this.id + "] Connection cannot be kept alive");
478                 }
479                 this.managedConn.unmarkReusable();
480                 if (this.proxyAuthState.getState() == AuthProtocolState.SUCCESS
481                         && this.proxyAuthState.getAuthScheme() != null
482                         && this.proxyAuthState.getAuthScheme().isConnectionBased()) {
483                     if (this.log.isDebugEnabled()) {
484                         this.log.debug("[exchange: " + this.id + "] Resetting proxy auth state");
485                     }
486                     this.proxyAuthState.reset();
487                 }
488                 if (this.targetAuthState.getState() == AuthProtocolState.SUCCESS
489                         && this.targetAuthState.getAuthScheme() != null
490                         && this.targetAuthState.getAuthScheme().isConnectionBased()) {
491                     if (this.log.isDebugEnabled()) {
492                         this.log.debug("[exchange: " + this.id + "] Resetting target auth state");
493                     }
494                     this.targetAuthState.reset();
495                 }
496             }
497 
498             if (this.finalResponse != null) {
499                 this.responseConsumer.responseCompleted(this.localContext);
500                 if (this.log.isDebugEnabled()) {
501                     this.log.debug("[exchange: " + this.id + "] Response processed");
502                 }
503                 releaseConnection();
504                 final T result = this.responseConsumer.getResult();
505                 final Exception ex = this.responseConsumer.getException();
506                 if (ex == null) {
507                     this.resultCallback.completed(result, this);
508                 } else {
509                     this.resultCallback.failed(ex, this);
510                 }
511             } else {
512                 if (this.followup != null) {
513                     final HttpRoute actualRoute = this.mainRequest.getRoute();
514                     final HttpRoute newRoute = this.followup.getRoute();
515                     if (!actualRoute.equals(newRoute)) {
516                         releaseConnection();
517                     }
518                     this.mainRequest = this.followup;
519                 }
520                 if (this.managedConn != null && !this.managedConn.isOpen()) {
521                     releaseConnection();
522                 }
523                 if (this.managedConn != null) {
524                     this.managedConn.requestOutput();
525                 } else {
526                     requestConnection();
527                 }
528             }
529             this.followup = null;
530             this.currentRequest = null;
531             this.currentResponse = null;
532         } catch (final RuntimeException runex) {
533             failed(runex);
534             throw runex;
535         }
536     }
537 
538     @Override
539     public synchronized boolean cancel() {
540         if (this.log.isDebugEnabled()) {
541             this.log.debug("[exchange: " + this.id + "] Cancelled");
542         }
543         try {
544             final boolean cancelled = this.responseConsumer.cancel();
545 
546             final T result = this.responseConsumer.getResult();
547             final Exception ex = this.responseConsumer.getException();
548             if (ex != null) {
549                 this.resultCallback.failed(ex, this);
550             } else if (result != null) {
551                 this.resultCallback.completed(result, this);
552             } else {
553                 this.resultCallback.cancelled(this);
554             }
555             return cancelled;
556         } catch (final RuntimeException runex) {
557             this.resultCallback.failed(runex, this);
558             throw runex;
559         } finally {
560             close();
561         }
562     }
563 
564     @Override
565     public boolean isDone() {
566         return this.resultCallback.isDone();
567     }
568 
569     @Override
570     public T getResult() {
571         return this.responseConsumer.getResult();
572     }
573 
574     @Override
575     public Exception getException() {
576         return this.responseConsumer.getException();
577     }
578 
579     private synchronized void connectionRequestCompleted(final ManagedClientAsyncConnection conn) {
580         if (this.log.isDebugEnabled()) {
581             this.log.debug("[exchange: " + this.id + "] Connection allocated: " + conn);
582         }
583         this.connRequestCallback = null;
584         try {
585             this.managedConn = conn;
586             if (this.closed) {
587                 conn.releaseConnection();
588                 return;
589             }
590             final HttpRoute route = this.mainRequest.getRoute();
591             if (!conn.isOpen()) {
592                 conn.open(route, this.localContext, this.params);
593             }
594             conn.getContext().setAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER, this);
595             conn.requestOutput();
596             this.routeEstablished = route.equals(conn.getRoute());
597             if (!conn.isOpen()) {
598                 throw new ConnectionClosedException("Connection closed");
599             }
600         } catch (final IOException ex) {
601             failed(ex);
602         } catch (final RuntimeException runex) {
603             failed(runex);
604             throw runex;
605         }
606     }
607 
608     private synchronized void connectionRequestFailed(final Exception ex) {
609         if (this.log.isDebugEnabled()) {
610             this.log.debug("[exchange: " + this.id + "] connection request failed");
611         }
612         this.connRequestCallback = null;
613         try {
614             this.resultCallback.failed(ex, this);
615         } finally {
616             close();
617         }
618     }
619 
620     private synchronized void connectionRequestCancelled() {
621         if (this.log.isDebugEnabled()) {
622             this.log.debug("[exchange: " + this.id + "] Connection request cancelled");
623         }
624         this.connRequestCallback = null;
625         try {
626             this.resultCallback.cancelled(this);
627         } finally {
628             close();
629         }
630     }
631 
632     class InternalFutureCallback implements FutureCallback<ManagedClientAsyncConnection> {
633 
634         @Override
635         public void completed(final ManagedClientAsyncConnection session) {
636             connectionRequestCompleted(session);
637         }
638 
639         @Override
640         public void failed(final Exception ex) {
641             connectionRequestFailed(ex);
642         }
643 
644         @Override
645         public void cancelled() {
646             connectionRequestCancelled();
647         }
648 
649     }
650 
651     private void requestConnection() {
652         final HttpRoute route = this.mainRequest.getRoute();
653         if (this.log.isDebugEnabled()) {
654             this.log.debug("[exchange: " + this.id + "] Request connection for " + route);
655         }
656         final long connectTimeout = HttpConnectionParams.getConnectionTimeout(this.params);
657         final Object userToken = this.localContext.getAttribute(ClientContext.USER_TOKEN);
658         this.connRequestCallback = new InternalFutureCallback();
659         this.connmgr.leaseConnection(
660                 route, userToken,
661                 connectTimeout, TimeUnit.MILLISECONDS,
662                 this.connRequestCallback);
663     }
664 
665     public synchronized void endOfStream() {
666         if (this.managedConn != null) {
667             if (this.log.isDebugEnabled()) {
668                 this.log.debug("[exchange: " + this.id + "] Unexpected end of data stream");
669             }
670             releaseConnection();
671             if (this.connRequestCallback == null) {
672                 requestConnection();
673             }
674         }
675     }
676 
677     protected HttpRoute determineRoute(
678             final HttpHost target,
679             final HttpRequest request,
680             final HttpContext context) throws HttpException {
681         final HttpHost t = target != null ? target :
682                 (HttpHost) request.getParams().getParameter(ClientPNames.DEFAULT_HOST);
683         if (t == null) {
684             throw new IllegalStateException("Target host could not be resolved");
685         }
686         return this.routePlanner.determineRoute(t, request, context);
687     }
688 
689     private RequestWrapper wrapRequest(final HttpRequest request) throws ProtocolException {
690         if (request instanceof HttpEntityEnclosingRequest) {
691             return new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request);
692         } else {
693             return new RequestWrapper(request);
694         }
695     }
696 
697     protected void rewriteRequestURI(
698             final RequestWrapper request, final HttpRoute route) throws ProtocolException {
699         try {
700             URI uri = request.getURI();
701             if (route.getProxyHost() != null && !route.isTunnelled()) {
702                 // Make sure the request URI is absolute
703                 if (!uri.isAbsolute()) {
704                     final HttpHost target = route.getTargetHost();
705                     uri = URIUtils.rewriteURI(uri, target);
706                     request.setURI(uri);
707                 }
708             } else {
709                 // Make sure the request URI is relative
710                 if (uri.isAbsolute()) {
711                     uri = URIUtils.rewriteURI(uri, null);
712                     request.setURI(uri);
713                 }
714             }
715         } catch (final URISyntaxException ex) {
716             throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex);
717         }
718     }
719 
720     private AsyncSchemeRegistry getSchemeRegistry(final HttpContext context) {
721         AsyncSchemeRegistry reg = (AsyncSchemeRegistry) context.getAttribute(
722                 ClientContext.SCHEME_REGISTRY);
723         if (reg == null) {
724             reg = this.connmgr.getSchemeRegistry();
725         }
726         return reg;
727     }
728 
729     private HttpRequest createConnectRequest(final HttpRoute route) {
730         // see RFC 2817, section 5.2 and
731         // INTERNET-DRAFT: Tunneling TCP based protocols through
732         // Web proxy servers
733         final HttpHost target = route.getTargetHost();
734         final String host = target.getHostName();
735         int port = target.getPort();
736         if (port < 0) {
737             final AsyncSchemeRegistry registry = getSchemeRegistry(this.localContext);
738             final AsyncScheme scheme = registry.getScheme(target.getSchemeName());
739             port = scheme.getDefaultPort();
740         }
741         final StringBuilder buffer = new StringBuilder(host.length() + 6);
742         buffer.append(host);
743         buffer.append(':');
744         buffer.append(Integer.toString(port));
745         final ProtocolVersion ver = HttpProtocolParams.getVersion(this.params);
746         final HttpRequest req = new BasicHttpRequest("CONNECT", buffer.toString(), ver);
747         return req;
748     }
749 
750     private RoutedRequest handleResponse() throws HttpException {
751         RoutedRequest followup = null;
752         if (HttpClientParams.isAuthenticating(this.params)) {
753             final CredentialsProvider credsProvider = (CredentialsProvider) this.localContext.getAttribute(
754                     ClientContext.CREDS_PROVIDER);
755             if (credsProvider != null) {
756                 followup = handleTargetChallenge(credsProvider);
757                 if (followup != null) {
758                     return followup;
759                 }
760                 followup = handleProxyChallenge(credsProvider);
761                 if (followup != null) {
762                     return followup;
763                 }
764             }
765         }
766         if (HttpClientParams.isRedirecting(this.params)) {
767             followup = handleRedirect();
768             if (followup != null) {
769                 return followup;
770             }
771         }
772         return null;
773     }
774 
775     private RoutedRequest handleConnectResponse() throws HttpException {
776         RoutedRequest followup = null;
777         if (HttpClientParams.isAuthenticating(this.params)) {
778             final CredentialsProvider credsProvider = (CredentialsProvider) this.localContext.getAttribute(
779                     ClientContext.CREDS_PROVIDER);
780             if (credsProvider != null) {
781                 followup = handleProxyChallenge(credsProvider);
782                 if (followup != null) {
783                     return followup;
784                 }
785             }
786         }
787         return null;
788     }
789 
790     private RoutedRequest handleRedirect() throws HttpException {
791         if (this.redirectStrategy.isRedirected(
792                 this.currentRequest, this.currentResponse, this.localContext)) {
793 
794             final HttpRoute route = this.mainRequest.getRoute();
795             final RequestWrapper request = this.mainRequest.getRequest();
796 
797             final int maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
798             if (this.redirectCount >= maxRedirects) {
799                 throw new RedirectException("Maximum redirects ("
800                         + maxRedirects + ") exceeded");
801             }
802             this.redirectCount++;
803 
804             final HttpUriRequest redirect = this.redirectStrategy.getRedirect(
805                     this.currentRequest, this.currentResponse, this.localContext);
806             final HttpRequest orig = request.getOriginal();
807             redirect.setHeaders(orig.getAllHeaders());
808 
809             final URI uri = redirect.getURI();
810             if (uri.getHost() == null) {
811                 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
812             }
813             final HttpHost newTarget = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
814 
815             // Reset auth states if redirecting to another host
816             if (!route.getTargetHost().equals(newTarget)) {
817                 if (this.log.isDebugEnabled()) {
818                     this.log.debug("[exchange: " + this.id + "] Resetting target auth state");
819                 }
820                 this.targetAuthState.reset();
821                 final AuthScheme authScheme = this.proxyAuthState.getAuthScheme();
822                 if (authScheme != null && authScheme.isConnectionBased()) {
823                     if (this.log.isDebugEnabled()) {
824                         this.log.debug("[exchange: " + this.id + "] Resetting proxy auth state");
825                     }
826                     this.proxyAuthState.reset();
827                 }
828             }
829 
830             final RequestWrapper newRequest = wrapRequest(redirect);
831             newRequest.setParams(this.params);
832 
833             final HttpRoute newRoute = determineRoute(newTarget, newRequest, this.localContext);
834 
835             if (this.log.isDebugEnabled()) {
836                 this.log.debug("[exchange: " + this.id + "] Redirecting to '" + uri + "' via " + newRoute);
837             }
838             return new RoutedRequest(newRequest, newRoute);
839         }
840         return null;
841     }
842 
843     private RoutedRequest handleTargetChallenge(
844             final CredentialsProvider credsProvider) throws HttpException {
845         final HttpRoute route = this.mainRequest.getRoute();
846         HttpHost target = (HttpHost) this.localContext.getAttribute(
847                 ExecutionContext.HTTP_TARGET_HOST);
848         if (target == null) {
849             target = route.getTargetHost();
850         }
851         if (this.authenticator.isAuthenticationRequested(target, this.currentResponse,
852                 this.targetAuthStrategy, this.targetAuthState, this.localContext)) {
853             if (this.authenticator.authenticate(target, this.currentResponse,
854                     this.targetAuthStrategy, this.targetAuthState, this.localContext)) {
855                 // Re-try the same request via the same route
856                 return this.mainRequest;
857             } else {
858                 return null;
859             }
860         }
861         return null;
862     }
863 
864     private RoutedRequest handleProxyChallenge(
865             final CredentialsProvider credsProvider) throws HttpException {
866         final HttpRoute route = this.mainRequest.getRoute();
867         final HttpHost proxy = route.getProxyHost();
868         if (this.authenticator.isAuthenticationRequested(proxy, this.currentResponse,
869                 this.proxyAuthStrategy, this.proxyAuthState, this.localContext)) {
870             if (this.authenticator.authenticate(proxy, this.currentResponse,
871                     this.proxyAuthStrategy, this.proxyAuthState, this.localContext)) {
872                 // Re-try the same request via the same route
873                 return this.mainRequest;
874             } else {
875                 return null;
876             }
877         }
878         return null;
879     }
880 
881     @Override
882     public HttpContext getContext() {
883         return this.localContext;
884     }
885 
886     @Override
887     public HttpProcessor getHttpProcessor() {
888         return this.httppocessor;
889     }
890 
891     @Override
892     public ConnectionReuseStrategy getConnectionReuseStrategy() {
893         return this.reuseStrategy;
894     }
895 
896 }