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 ioControl) 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, ioControl);
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 ioControl) 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, ioControl);
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         return request instanceof HttpEntityEnclosingRequest
691                         ? new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request)
692                         : new RequestWrapper(request);
693     }
694 
695     protected void rewriteRequestURI(
696             final RequestWrapper request, final HttpRoute route) throws ProtocolException {
697         try {
698             URI uri = request.getURI();
699             if (route.getProxyHost() != null && !route.isTunnelled()) {
700                 // Make sure the request URI is absolute
701                 if (!uri.isAbsolute()) {
702                     final HttpHost target = route.getTargetHost();
703                     uri = URIUtils.rewriteURI(uri, target);
704                     request.setURI(uri);
705                 }
706             } else {
707                 // Make sure the request URI is relative
708                 if (uri.isAbsolute()) {
709                     uri = URIUtils.rewriteURI(uri, null);
710                     request.setURI(uri);
711                 }
712             }
713         } catch (final URISyntaxException ex) {
714             throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex);
715         }
716     }
717 
718     private AsyncSchemeRegistry getSchemeRegistry(final HttpContext context) {
719         AsyncSchemeRegistry./../../org/apache/http/nio/conn/scheme/AsyncSchemeRegistry.html#AsyncSchemeRegistry">AsyncSchemeRegistry reg = (AsyncSchemeRegistry) context.getAttribute(
720                 ClientContext.SCHEME_REGISTRY);
721         if (reg == null) {
722             reg = this.connmgr.getSchemeRegistry();
723         }
724         return reg;
725     }
726 
727     private HttpRequest createConnectRequest(final HttpRoute route) {
728         // see RFC 2817, section 5.2 and
729         // INTERNET-DRAFT: Tunneling TCP based protocols through
730         // Web proxy servers
731         final HttpHost target = route.getTargetHost();
732         final String host = target.getHostName();
733         int port = target.getPort();
734         if (port < 0) {
735             final AsyncSchemeRegistry registry = getSchemeRegistry(this.localContext);
736             final AsyncScheme scheme = registry.getScheme(target.getSchemeName());
737             port = scheme.getDefaultPort();
738         }
739         final StringBuilder buffer = new StringBuilder(host.length() + 6);
740         buffer.append(host);
741         buffer.append(':');
742         buffer.append(Integer.toString(port));
743         final ProtocolVersion ver = HttpProtocolParams.getVersion(this.params);
744         final HttpRequest req = new BasicHttpRequest("CONNECT", buffer.toString(), ver);
745         return req;
746     }
747 
748     private RoutedRequest handleResponse() throws HttpException {
749         RoutedRequest followup = null;
750         if (HttpClientParams.isAuthenticating(this.params)) {
751             final CredentialsProvider credsProvider = (CredentialsProvider) this.localContext.getAttribute(
752                     ClientContext.CREDS_PROVIDER);
753             if (credsProvider != null) {
754                 followup = handleTargetChallenge(credsProvider);
755                 if (followup != null) {
756                     return followup;
757                 }
758                 followup = handleProxyChallenge(credsProvider);
759                 if (followup != null) {
760                     return followup;
761                 }
762             }
763         }
764         if (HttpClientParams.isRedirecting(this.params)) {
765             followup = handleRedirect();
766             if (followup != null) {
767                 return followup;
768             }
769         }
770         return null;
771     }
772 
773     private RoutedRequest handleConnectResponse() {
774         RoutedRequest followup = null;
775         if (HttpClientParams.isAuthenticating(this.params)) {
776             final CredentialsProvider credsProvider = (CredentialsProvider) this.localContext.getAttribute(
777                     ClientContext.CREDS_PROVIDER);
778             if (credsProvider != null) {
779                 followup = handleProxyChallenge(credsProvider);
780                 if (followup != null) {
781                     return followup;
782                 }
783             }
784         }
785         return null;
786     }
787 
788     private RoutedRequest handleRedirect() throws HttpException {
789         if (this.redirectStrategy.isRedirected(
790                 this.currentRequest, this.currentResponse, this.localContext)) {
791 
792             final HttpRoute route = this.mainRequest.getRoute();
793             final RequestWrapper request = this.mainRequest.getRequest();
794 
795             final int maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
796             if (this.redirectCount >= maxRedirects) {
797                 throw new RedirectException("Maximum redirects ("
798                         + maxRedirects + ") exceeded");
799             }
800             this.redirectCount++;
801 
802             final HttpUriRequest redirect = this.redirectStrategy.getRedirect(
803                     this.currentRequest, this.currentResponse, this.localContext);
804             final HttpRequest orig = request.getOriginal();
805             redirect.setHeaders(orig.getAllHeaders());
806 
807             final URI uri = redirect.getURI();
808             if (uri.getHost() == null) {
809                 throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
810             }
811             final HttpHost newTarget = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
812 
813             // Reset auth states if redirecting to another host
814             if (!route.getTargetHost().equals(newTarget)) {
815                 if (this.log.isDebugEnabled()) {
816                     this.log.debug("[exchange: " + this.id + "] Resetting target auth state");
817                 }
818                 this.targetAuthState.reset();
819                 final AuthScheme authScheme = this.proxyAuthState.getAuthScheme();
820                 if (authScheme != null && authScheme.isConnectionBased()) {
821                     if (this.log.isDebugEnabled()) {
822                         this.log.debug("[exchange: " + this.id + "] Resetting proxy auth state");
823                     }
824                     this.proxyAuthState.reset();
825                 }
826             }
827 
828             final RequestWrapper newRequest = wrapRequest(redirect);
829             newRequest.setParams(this.params);
830 
831             final HttpRoute newRoute = determineRoute(newTarget, newRequest, this.localContext);
832 
833             if (this.log.isDebugEnabled()) {
834                 this.log.debug("[exchange: " + this.id + "] Redirecting to '" + uri + "' via " + newRoute);
835             }
836             return new RoutedRequest(newRequest, newRoute);
837         }
838         return null;
839     }
840 
841     private RoutedRequest handleTargetChallenge(
842             final CredentialsProvider credsProvider) {
843         final HttpRoute route = this.mainRequest.getRoute();
844         HttpHost target = (HttpHost) this.localContext.getAttribute(
845                 ExecutionContext.HTTP_TARGET_HOST);
846         if (target == null) {
847             target = route.getTargetHost();
848         }
849         if (this.authenticator.isAuthenticationRequested(target, this.currentResponse,
850                 this.targetAuthStrategy, this.targetAuthState, this.localContext)) {
851             if (this.authenticator.authenticate(target, this.currentResponse,
852                     this.targetAuthStrategy, this.targetAuthState, this.localContext)) {
853                 // Re-try the same request via the same route
854                 return this.mainRequest;
855             }
856             return null;
857         }
858         return null;
859     }
860 
861     private RoutedRequest handleProxyChallenge(
862             final CredentialsProvider credsProvider) {
863         final HttpRoute route = this.mainRequest.getRoute();
864         final HttpHost proxy = route.getProxyHost();
865         if (this.authenticator.isAuthenticationRequested(proxy, this.currentResponse,
866                 this.proxyAuthStrategy, this.proxyAuthState, this.localContext)) {
867             if (this.authenticator.authenticate(proxy, this.currentResponse,
868                     this.proxyAuthStrategy, this.proxyAuthState, this.localContext)) {
869                 // Re-try the same request via the same route
870                 return this.mainRequest;
871             }
872             return null;
873         }
874         return null;
875     }
876 
877     @Override
878     public HttpContext getContext() {
879         return this.localContext;
880     }
881 
882     @Override
883     public HttpProcessor getHttpProcessor() {
884         return this.httpPocessor;
885     }
886 
887     @Override
888     public ConnectionReuseStrategy getConnectionReuseStrategy() {
889         return this.reuseStrategy;
890     }
891 
892 }