org.apache.http.impl.nio.client.MainClientExec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of httpasyncclient Show documentation
Show all versions of httpasyncclient Show documentation
Apache HttpComponents AsyncClient
The newest version!
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.http.impl.nio.client;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolException;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthenticationStrategy;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.NonRepeatableRequestException;
import org.apache.http.client.RedirectException;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.Configurable;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.protocol.RequestClientConnControl;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.conn.routing.BasicRouteDirector;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRouteDirector;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.impl.auth.HttpAuthenticator;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestTargetHost;
class MainClientExec implements InternalClientExec {
private final Log log = LogFactory.getLog(getClass());
private final HttpProcessor httpProcessor;
private final HttpProcessor proxyHttpProcessor;
private final HttpRoutePlanner routePlanner;
private final AuthenticationStrategy targetAuthStrategy;
private final AuthenticationStrategy proxyAuthStrategy;
private final UserTokenHandler userTokenHandler;
private final RedirectStrategy redirectStrategy;
private final HttpRouteDirector routeDirector;
private final HttpAuthenticator authenticator;
public MainClientExec(
final HttpProcessor httpProcessor,
final HttpRoutePlanner routePlanner,
final RedirectStrategy redirectStrategy,
final AuthenticationStrategy targetAuthStrategy,
final AuthenticationStrategy proxyAuthStrategy,
final UserTokenHandler userTokenHandler) {
super();
this.httpProcessor = httpProcessor;
this.proxyHttpProcessor = new ImmutableHttpProcessor(
new RequestTargetHost(), new RequestClientConnControl());
this.routePlanner = routePlanner;
this.redirectStrategy = redirectStrategy;
this.targetAuthStrategy = targetAuthStrategy;
this.proxyAuthStrategy = proxyAuthStrategy;
this.userTokenHandler = userTokenHandler;
this.routeDirector = new BasicRouteDirector();
this.authenticator = new HttpAuthenticator(log);
}
@Override
public void prepare(
final HttpHost target,
final HttpRequest original,
final InternalState state,
final AbstractClientExchangeHandler handler) throws HttpException, IOException {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] start execution");
}
final HttpClientContext localContext = state.getLocalContext();
if (original instanceof Configurable) {
final RequestConfig config = ((Configurable) original).getConfig();
if (config != null) {
localContext.setRequestConfig(config);
}
}
final List redirectLocations = localContext.getRedirectLocations();
if (redirectLocations != null) {
redirectLocations.clear();
}
final HttpRequestWrapper request = HttpRequestWrapper.wrap(original);
final HttpRoute route = this.routePlanner.determineRoute(target, request, localContext);
handler.setRoute(route);
state.setMainRequest(request);
handler.setCurrentRequest(request);
prepareRequest(state, handler);
}
@Override
public HttpRequest generateRequest(
final InternalState state,
final AbstractClientExchangeHandler handler) throws IOException, HttpException {
final HttpRoute route = handler.getRoute();
handler.verifytRoute();
if (!handler.isRouteEstablished()) {
int step;
loop:
do {
final HttpRoute fact = handler.getActualRoute();
step = this.routeDirector.nextStep(route, fact);
switch (step) {
case HttpRouteDirector.CONNECT_TARGET:
handler.onRouteToTarget();
break;
case HttpRouteDirector.CONNECT_PROXY:
handler.onRouteToProxy();
break;
case HttpRouteDirector.TUNNEL_TARGET:
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Tunnel required");
}
final HttpRequest connect = createConnectRequest(route, state);
handler.setCurrentRequest(HttpRequestWrapper.wrap(connect));
break loop;
case HttpRouteDirector.TUNNEL_PROXY:
throw new HttpException("Proxy chains are not supported");
case HttpRouteDirector.LAYER_PROTOCOL:
handler.onRouteUpgrade();
break;
case HttpRouteDirector.UNREACHABLE:
throw new HttpException("Unable to establish route: " +
"planned = " + route + "; current = " + fact);
case HttpRouteDirector.COMPLETE:
handler.onRouteComplete();
this.log.debug("[exchange: " + state.getId() + "] Connection route established");
break;
default:
throw new IllegalStateException("Unknown step indicator "
+ step + " from RouteDirector.");
}
} while (step > HttpRouteDirector.COMPLETE);
}
final HttpClientContext localContext = state.getLocalContext();
HttpRequestWrapper currentRequest = handler.getCurrentRequest();
if (currentRequest == null) {
currentRequest = state.getMainRequest();
handler.setCurrentRequest(currentRequest);
}
if (handler.isRouteEstablished()) {
state.incrementExecCount();
if (state.getExecCount() > 1) {
final HttpAsyncRequestProducer requestProducer = state.getRequestProducer();
if (!requestProducer.isRepeatable() && state.isRequestContentProduced()) {
throw new NonRepeatableRequestException("Cannot retry request " +
"with a non-repeatable request entity.");
}
requestProducer.resetRequest();
}
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Attempt " + state.getExecCount() +
" to execute request");
}
if (!currentRequest.containsHeader(AUTH.WWW_AUTH_RESP)) {
final AuthState targetAuthState = localContext.getTargetAuthState();
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Target auth state: " + targetAuthState.getState());
}
this.authenticator.generateAuthResponse(currentRequest, targetAuthState, localContext);
}
if (!currentRequest.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) {
final AuthState proxyAuthState = localContext.getProxyAuthState();
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Proxy auth state: " + proxyAuthState.getState());
}
this.authenticator.generateAuthResponse(currentRequest, proxyAuthState, localContext);
}
} else {
if (!currentRequest.containsHeader(AUTH.PROXY_AUTH_RESP)) {
final AuthState proxyAuthState = localContext.getProxyAuthState();
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Proxy auth state: " + proxyAuthState.getState());
}
this.authenticator.generateAuthResponse(currentRequest, proxyAuthState, localContext);
}
}
final NHttpClientConnection managedConn = handler.getConnection();
localContext.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
final RequestConfig config = localContext.getRequestConfig();
if (config.getSocketTimeout() > 0) {
managedConn.setSocketTimeout(config.getSocketTimeout());
}
return currentRequest;
}
@Override
public void produceContent(
final InternalState state,
final ContentEncoder encoder,
final IOControl ioControl) throws IOException {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] produce content");
}
final HttpAsyncRequestProducer requestProducer = state.getRequestProducer();
state.setRequestContentProduced();
requestProducer.produceContent(encoder, ioControl);
if (encoder.isCompleted()) {
requestProducer.resetRequest();
}
}
@Override
public void requestCompleted(
final InternalState state,
final AbstractClientExchangeHandler handler) {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Request completed");
}
final HttpClientContext localContext = state.getLocalContext();
final HttpAsyncRequestProducer requestProducer = state.getRequestProducer();
requestProducer.requestCompleted(localContext);
}
@Override
public void responseReceived(
final HttpResponse response,
final InternalState state,
final AbstractClientExchangeHandler handler) throws IOException, HttpException {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Response received " + response.getStatusLine());
}
final HttpClientContext context = state.getLocalContext();
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
this.httpProcessor.process(response, context);
handler.setCurrentResponse(response);
if (!handler.isRouteEstablished()) {
final int status = response.getStatusLine().getStatusCode();
if (status < 200) {
throw new HttpException("Unexpected response to CONNECT request: " +
response.getStatusLine());
}
if (status == HttpStatus.SC_OK) {
handler.onRouteTunnelToTarget();
handler.setCurrentRequest(null);
} else {
if (!handleConnectResponse(state, handler)) {
state.setFinalResponse(response);
}
}
} else {
if (!handleResponse(state, handler)) {
state.setFinalResponse(response);
}
}
if (state.getFinalResponse() != null) {
final HttpAsyncResponseConsumer responseConsumer = state.getResponseConsumer();
responseConsumer.responseReceived(response);
}
}
@Override
public void consumeContent(
final InternalState state,
final ContentDecoder decoder,
final IOControl ioControl) throws IOException {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Consume content");
}
if (state.getFinalResponse() != null) {
final HttpAsyncResponseConsumer responseConsumer = state.getResponseConsumer();
responseConsumer.consumeContent(decoder, ioControl);
} else {
final ByteBuffer tmpbuf = state.getTmpbuf();
tmpbuf.clear();
decoder.read(tmpbuf);
}
}
@Override
public void responseCompleted(
final InternalState state,
final AbstractClientExchangeHandler handler) throws IOException, HttpException {
final HttpClientContext localContext = state.getLocalContext();
final HttpResponse currentResponse = handler.getCurrentResponse();
if (!handler.isRouteEstablished()) {
final int status = currentResponse.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK) {
handler.setCurrentResponse(null);
return;
}
}
final boolean keepAlive = handler.manageConnectionPersistence();
if (!keepAlive) {
handler.releaseConnection();
final AuthState proxyAuthState = localContext.getProxyAuthState();
if (proxyAuthState.getState() == AuthProtocolState.SUCCESS
&& proxyAuthState.getAuthScheme() != null
&& proxyAuthState.getAuthScheme().isConnectionBased()) {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Resetting proxy auth state");
}
proxyAuthState.reset();
}
final AuthState targetAuthState = localContext.getTargetAuthState();
if (targetAuthState.getState() == AuthProtocolState.SUCCESS
&& targetAuthState.getAuthScheme() != null
&& targetAuthState.getAuthScheme().isConnectionBased()) {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Resetting target auth state");
}
targetAuthState.reset();
}
}
Object userToken = localContext.getUserToken();
if (userToken == null) {
userToken = this.userTokenHandler.getUserToken(localContext);
localContext.setAttribute(HttpClientContext.USER_TOKEN, userToken);
}
if (state.getFinalResponse() != null) {
final HttpAsyncResponseConsumer responseConsumer = state.getResponseConsumer();
responseConsumer.responseCompleted(localContext);
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Response processed");
}
handler.releaseConnection();
} else {
if (state.getRedirect() != null) {
final HttpUriRequest redirect = state.getRedirect();
final URI uri = redirect.getURI();
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Redirecting to '" + uri + "'");
}
state.setRedirect(null);
final HttpHost newTarget = URIUtils.extractHost(uri);
if (newTarget == null) {
throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri);
}
// Reset auth states if redirecting to another host
final HttpRoute route = handler.getRoute();
if (!route.getTargetHost().equals(newTarget)) {
final AuthState targetAuthState = localContext.getTargetAuthState();
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Resetting target auth state");
}
targetAuthState.reset();
final AuthState proxyAuthState = localContext.getProxyAuthState();
final AuthScheme authScheme = proxyAuthState.getAuthScheme();
if (authScheme != null && authScheme.isConnectionBased()) {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Resetting proxy auth state");
}
proxyAuthState.reset();
}
}
if (!redirect.headerIterator().hasNext()) {
final HttpRequest original = state.getMainRequest().getOriginal();
redirect.setHeaders(original.getAllHeaders());
}
final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(redirect);
final HttpRoute newRoute = this.routePlanner.determineRoute(
newTarget, newRequest, localContext);
if (!route.equals(newRoute)) {
handler.releaseConnection();
}
handler.setRoute(newRoute);
handler.setCurrentRequest(newRequest);
state.setMainRequest(newRequest);
prepareRequest(state, handler);
}
}
handler.setCurrentResponse(null);
}
private void rewriteRequestURI(
final HttpRequestWrapper request,
final HttpRoute route) throws ProtocolException {
try {
URI uri = request.getURI();
if (uri != null) {
if (route.getProxyHost() != null && !route.isTunnelled()) {
// Make sure the request URI is absolute
if (!uri.isAbsolute()) {
final HttpHost target = route.getTargetHost();
uri = URIUtils.rewriteURI(uri, target, true);
} else {
uri = URIUtils.rewriteURI(uri);
}
} else {
// Make sure the request URI is relative
if (uri.isAbsolute()) {
uri = URIUtils.rewriteURI(uri, null, true);
} else {
uri = URIUtils.rewriteURI(uri);
}
}
request.setURI(uri);
}
} catch (final URISyntaxException ex) {
throw new ProtocolException("Invalid URI: " +
request.getRequestLine().getUri(), ex);
}
}
private void prepareRequest(
final InternalState state,
final AbstractClientExchangeHandler handler) throws IOException, HttpException {
final HttpClientContext localContext = state.getLocalContext();
final HttpRequestWrapper currentRequest = handler.getCurrentRequest();
final HttpRoute route = handler.getRoute();
final HttpRequest original = currentRequest.getOriginal();
URI uri = null;
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
} else {
final String uriString = original.getRequestLine().getUri();
try {
uri = URI.create(uriString);
} catch (final IllegalArgumentException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug("[exchange: " + state.getId() + "] Unable to parse '" + uriString +
"' as a valid URI; request URI and Host header may be inconsistent", ex);
}
}
}
currentRequest.setURI(uri);
// Re-write request URI if needed
rewriteRequestURI(currentRequest, route);
HttpHost target = null;
if (uri != null && uri.isAbsolute() && uri.getHost() != null) {
target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
if (target == null) {
target = route.getTargetHost();
}
// Get user info from the URI
if (uri != null) {
final String userinfo = uri.getUserInfo();
if (userinfo != null) {
final CredentialsProvider credsProvider = localContext.getCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(target),
new UsernamePasswordCredentials(userinfo));
}
}
localContext.setAttribute(HttpCoreContext.HTTP_REQUEST, currentRequest);
localContext.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
localContext.setAttribute(HttpClientContext.HTTP_ROUTE, route);
this.httpProcessor.process(currentRequest, localContext);
}
private HttpRequest createConnectRequest(
final HttpRoute route, final InternalState state) throws IOException, HttpException {
// see RFC 2817, section 5.2 and
// INTERNET-DRAFT: Tunneling TCP based protocols through
// Web proxy servers
final HttpHost target = route.getTargetHost();
final String host = target.getHostName();
final int port = target.getPort();
final StringBuilder buffer = new StringBuilder(host.length() + 6);
buffer.append(host);
buffer.append(':');
buffer.append(Integer.toString(port));
final HttpRequest request = new BasicHttpRequest("CONNECT", buffer.toString(), HttpVersion.HTTP_1_1);
final HttpClientContext localContext = state.getLocalContext();
this.proxyHttpProcessor.process(request, localContext);
return request;
}
private boolean handleConnectResponse(
final InternalState state,
final AbstractClientExchangeHandler handler) {
final HttpClientContext localContext = state.getLocalContext();
final RequestConfig config = localContext.getRequestConfig();
if (config.isAuthenticationEnabled()) {
final CredentialsProvider credsProvider = localContext.getCredentialsProvider();
if (credsProvider != null) {
final HttpRoute route = handler.getRoute();
final HttpHost proxy = route.getProxyHost();
final HttpResponse currentResponse = handler.getCurrentResponse();
final AuthState proxyAuthState = localContext.getProxyAuthState();
if (this.authenticator.isAuthenticationRequested(proxy, currentResponse,
this.proxyAuthStrategy, proxyAuthState, localContext)) {
return this.authenticator.handleAuthChallenge(proxy, currentResponse,
this.proxyAuthStrategy, proxyAuthState, localContext);
}
}
}
return false;
}
private boolean handleResponse(
final InternalState state,
final AbstractClientExchangeHandler handler) throws HttpException {
final HttpClientContext localContext = state.getLocalContext();
final RequestConfig config = localContext.getRequestConfig();
if (config.isAuthenticationEnabled()) {
if (needAuthentication(state, handler)) {
// discard previous auth headers
final HttpRequestWrapper currentRequest = handler.getCurrentRequest();
final HttpRequest original = currentRequest.getOriginal();
if (!original.containsHeader(AUTH.WWW_AUTH_RESP)) {
currentRequest.removeHeaders(AUTH.WWW_AUTH_RESP);
}
if (!original.containsHeader(AUTH.PROXY_AUTH_RESP)) {
currentRequest.removeHeaders(AUTH.PROXY_AUTH_RESP);
}
return true;
}
}
if (config.isRedirectsEnabled()) {
final HttpRequestWrapper currentRequest = handler.getCurrentRequest();
final HttpResponse currentResponse = handler.getCurrentResponse();
if (this.redirectStrategy.isRedirected(currentRequest, currentResponse, localContext)) {
final int maxRedirects = config.getMaxRedirects() >= 0 ? config.getMaxRedirects() : 100;
if (state.getRedirectCount() >= maxRedirects) {
throw new RedirectException("Maximum redirects (" + maxRedirects + ") exceeded");
}
state.incrementRedirectCount();
final HttpUriRequest redirect = this.redirectStrategy.getRedirect(currentRequest.getOriginal(), currentResponse,
localContext);
state.setRedirect(redirect);
return true;
}
}
return false;
}
private boolean needAuthentication(
final InternalState state,
final AbstractClientExchangeHandler handler) {
final HttpClientContext localContext = state.getLocalContext();
final CredentialsProvider credsProvider = localContext.getCredentialsProvider();
if (credsProvider != null) {
final HttpRoute route = handler.getRoute();
final HttpResponse currentResponse = handler.getCurrentResponse();
HttpHost target = localContext.getTargetHost();
if (target == null) {
target = route.getTargetHost();
}
if (target.getPort() < 0) {
target = new HttpHost(
target.getHostName(),
route.getTargetHost().getPort(),
target.getSchemeName());
}
final AuthState targetAuthState = localContext.getTargetAuthState();
final AuthState proxyAuthState = localContext.getProxyAuthState();
final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested(
target, currentResponse, this.targetAuthStrategy, targetAuthState, localContext);
HttpHost proxy = route.getProxyHost();
// if proxy is not set use target host instead
if (proxy == null) {
proxy = route.getTargetHost();
}
final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested(
proxy, currentResponse, this.proxyAuthStrategy, proxyAuthState, localContext);
if (targetAuthRequested) {
return this.authenticator.handleAuthChallenge(target, currentResponse,
this.targetAuthStrategy, targetAuthState, localContext);
}
if (proxyAuthRequested) {
return this.authenticator.handleAuthChallenge(proxy, currentResponse,
this.proxyAuthStrategy, proxyAuthState, localContext);
}
}
return false;
}
}