1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
310 rewriteRequestURI(this.currentRequest, route);
311 }
312
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
703 if (!uri.isAbsolute()) {
704 final HttpHost target = route.getTargetHost();
705 uri = URIUtils.rewriteURI(uri, target);
706 request.setURI(uri);
707 }
708 } else {
709
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
731
732
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
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
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
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 }