/*
* Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.ning.http.client.providers.apache;
import com.ning.http.client.AsyncHandler;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpProvider;
import com.ning.http.client.AsyncHttpProviderConfig;
import com.ning.http.client.Body;
import com.ning.http.client.ByteArrayPart;
import com.ning.http.client.Cookie;
import com.ning.http.client.FilePart;
import com.ning.http.client.HttpResponseBodyPart;
import com.ning.http.client.HttpResponseHeaders;
import com.ning.http.client.HttpResponseStatus;
import com.ning.http.client.ListenableFuture;
import com.ning.http.client.MaxRedirectException;
import com.ning.http.client.Part;
import com.ning.http.client.PerRequestConfig;
import com.ning.http.client.ProgressAsyncHandler;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Realm;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.StringPart;
import com.ning.http.client.filter.FilterContext;
import com.ning.http.client.filter.FilterException;
import com.ning.http.client.filter.IOExceptionFilter;
import com.ning.http.client.filter.ResponseFilter;
import com.ning.http.client.listener.TransferCompletionHandler;
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider;
import com.ning.http.client.resumable.ResumableAsyncHandler;
import com.ning.http.util.AsyncHttpProviderUtils;
import com.ning.http.util.ProxyUtils;
import com.ning.http.util.UTF8UrlEncoder;
import org.apache.commons.httpclient.CircularRedirectException;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.httpclient.ProxyHost;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.PartSource;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
import static com.ning.http.util.AsyncHttpProviderUtils.DEFAULT_CHARSET;
/**
* An {@link com.ning.http.client.AsyncHttpProvider} for Apache Http Client 3.1
*/
public class ApacheAsyncHttpProvider implements AsyncHttpProvider {
private final static Logger logger = LoggerFactory.getLogger(ApacheAsyncHttpProvider.class);
private final AsyncHttpClientConfig config;
private final AtomicBoolean isClose = new AtomicBoolean(false);
private IdleConnectionTimeoutThread idleConnectionTimeoutThread;
private final AtomicInteger maxConnections = new AtomicInteger();
private final MultiThreadedHttpConnectionManager connectionManager;
private final HttpClientParams params;
static {
final SocketFactory factory = new TrustingSSLSocketFactory();
Protocol.registerProtocol("https", new Protocol("https", new ProtocolSocketFactory() {
public Socket createSocket(String string, int i, InetAddress inetAddress, int i1) throws IOException {
return factory.createSocket(string, i, inetAddress, i1);
}
public Socket createSocket(String string, int i, InetAddress inetAddress, int i1, HttpConnectionParams httpConnectionParams)
throws IOException {
return factory.createSocket(string, i, inetAddress, i1);
}
public Socket createSocket(String string, int i) throws IOException {
return factory.createSocket(string, i);
}
}, 443));
}
public ApacheAsyncHttpProvider(AsyncHttpClientConfig config) {
this.config = config;
connectionManager = new MultiThreadedHttpConnectionManager();
params = new HttpClientParams();
params.setParameter(HttpMethodParams.SINGLE_COOKIE_HEADER, Boolean.TRUE);
params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
AsyncHttpProviderConfig<?, ?> providerConfig = config.getAsyncHttpProviderConfig();
if (providerConfig != null && ApacheAsyncHttpProvider.class.isAssignableFrom(providerConfig.getClass())) {
configure(ApacheAsyncHttpProviderConfig.class.cast(providerConfig));
}
}
private void configure(ApacheAsyncHttpProviderConfig config) {
}
public <T> ListenableFuture<T> execute(Request request, AsyncHandler<T> handler) throws IOException {
if (isClose.get()) {
throw new IOException("Closed");
}
if (ResumableAsyncHandler.class.isAssignableFrom(handler.getClass())) {
request = ResumableAsyncHandler.class.cast(handler).adjustRequestRange(request);
}
if (config.getMaxTotalConnections() > -1 && (maxConnections.get() + 1) > config.getMaxTotalConnections()) {
throw new IOException(String.format("Too many connections %s", config.getMaxTotalConnections()));
}
if (idleConnectionTimeoutThread != null) {
idleConnectionTimeoutThread.shutdown();
idleConnectionTimeoutThread = null;
}
int requestTimeout = requestTimeout(config, request.getPerRequestConfig());
if (config.getIdleConnectionTimeoutInMs() > 0 && requestTimeout != -1 && requestTimeout < config.getIdleConnectionTimeoutInMs()) {
idleConnectionTimeoutThread = new IdleConnectionTimeoutThread();
idleConnectionTimeoutThread.setConnectionTimeout(config.getIdleConnectionTimeoutInMs());
idleConnectionTimeoutThread.addConnectionManager(connectionManager);
idleConnectionTimeoutThread.start();
}
HttpClient httpClient = new HttpClient(params, connectionManager);
Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm();
if (realm != null) {
httpClient.getParams().setAuthenticationPreemptive(realm.getUsePreemptiveAuth());
Credentials defaultcreds = new UsernamePasswordCredentials(realm.getPrincipal(), realm.getPassword());
httpClient.getState().setCredentials(new AuthScope(null, -1, AuthScope.ANY_REALM), defaultcreds);
}
HttpMethodBase method = createMethod(httpClient, request);
ApacheResponseFuture f = new ApacheResponseFuture<T>(handler, requestTimeout, request, method);
f.touch();
f.setInnerFuture(config.executorService().submit(new ApacheClientRunnable(request, handler, method, f, httpClient)));
maxConnections.incrementAndGet();
return f;
}
public void close() {
if (idleConnectionTimeoutThread != null) {
idleConnectionTimeoutThread.shutdown();
idleConnectionTimeoutThread = null;
}
if (connectionManager != null) {
try {
connectionManager.shutdown();
} catch (Exception e) {
logger.error("Error shutting down connection manager", e);
}
}
}
public Response prepareResponse(HttpResponseStatus status, HttpResponseHeaders headers, Collection<HttpResponseBodyPart> bodyParts) {
return new ApacheResponse(status, headers, bodyParts);
}
private HttpMethodBase createMethod(HttpClient client, Request request) throws IOException, FileNotFoundException {
String methodName = request.getMethod();
HttpMethodBase method = null;
if (methodName.equalsIgnoreCase("POST") || methodName.equalsIgnoreCase("PUT")) {
EntityEnclosingMethod post = methodName.equalsIgnoreCase("POST") ? new PostMethod(request.getUrl()) : new PutMethod(request.getUrl());
String bodyCharset = request.getBodyEncoding() == null ? DEFAULT_CHARSET : request.getBodyEncoding();
post.getParams().setContentCharset("ISO-8859-1");
if (request.getByteData() != null) {
post.setRequestEntity(new ByteArrayRequestEntity(request.getByteData()));
post.setRequestHeader("Content-Length", String.valueOf(request.getByteData().length));
} else if (request.getStringData() != null) {
post.setRequestEntity(new StringRequestEntity(request.getStringData(), "text/xml", bodyCharset));
post.setRequestHeader("Content-Length", String.valueOf(request.getStringData().getBytes(bodyCharset).length));
} else if (request.getStreamData() != null) {
InputStreamRequestEntity r = new InputStreamRequestEntity(request.getStreamData());
post.setRequestEntity(r);
post.setRequestHeader("Content-Length", String.valueOf(r.getContentLength()));
} else if (request.getParams() != null) {
StringBuilder sb = new StringBuilder();
for (final Map.Entry<String, List<String>> paramEntry : request.getParams()) {
final String key = paramEntry.getKey();
for (final String value : paramEntry.getValue()) {
if (sb.length() > 0) {
sb.append("&");
}
UTF8UrlEncoder.appendEncoded(sb, key);
sb.append("=");
UTF8UrlEncoder.appendEncoded(sb, value);
}
}
post.setRequestHeader("Content-Length", String.valueOf(sb.length()));
post.setRequestEntity(new StringRequestEntity(sb.toString(), "text/xml", "ISO-8859-1"));
if (!request.getHeaders().containsKey("Content-Type")) {
post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
} else if (request.getParts() != null) {
MultipartRequestEntity mre = createMultipartRequestEntity(bodyCharset, request.getParts(), post.getParams());
post.setRequestEntity(mre);
post.setRequestHeader("Content-Type", mre.getContentType());
post.setRequestHeader("Content-Length", String.valueOf(mre.getContentLength()));
} else if (request.getEntityWriter() != null) {
post.setRequestEntity(new EntityWriterRequestEntity(request.getEntityWriter(), computeAndSetContentLength(request, post)));
} else if (request.getFile() != null) {
File file = request.getFile();
if (!file.isFile()) {
throw new IOException(String.format(Thread.currentThread()
+ "File %s is not a file or doesn't exist", file.getAbsolutePath()));
}
post.setRequestHeader("Content-Length", String.valueOf(file.length()));
FileInputStream fis = new FileInputStream(file);
try {
InputStreamRequestEntity r = new InputStreamRequestEntity(fis);
post.setRequestEntity(r);
post.setRequestHeader("Content-Length", String.valueOf(r.getContentLength()));
} finally {
fis.close();
}
} else if (request.getBodyGenerator() != null) {
Body body = request.getBodyGenerator().createBody();
try {
int length = (int) body.getContentLength();
if (length < 0) {
length = (int) request.getContentLength();
}
// TODO: This is suboptimal
if (length >= 0) {
post.setRequestHeader("Content-Length", String.valueOf(length));
// This is totally sub optimal
byte[] bytes = new byte[length];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
for (; ;) {
buffer.clear();
if (body.read(buffer) < 0) {
break;
}
}
post.setRequestEntity(new ByteArrayRequestEntity(bytes));
}
} finally {
try {
body.close();
} catch (IOException e) {
logger.warn("Failed to close request body: {}", e.getMessage(), e);
}
}
}
if (request.getHeaders().getFirstValue("Expect") != null
&& request.getHeaders().getFirstValue("Expect").equalsIgnoreCase("100-Continue")) {
post.setUseExpectHeader(true);
}
method = post;
} else if (methodName.equalsIgnoreCase("DELETE")) {
method = new DeleteMethod(request.getUrl());
} else if (methodName.equalsIgnoreCase("HEAD")) {
method = new HeadMethod(request.getUrl());
} else if (methodName.equalsIgnoreCase("GET")) {
method = new GetMethod(request.getUrl());
} else if (methodName.equalsIgnoreCase("OPTIONS")) {
method = new OptionsMethod(request.getUrl());
} else {
throw new IllegalStateException(String.format("Invalid Method", methodName));
}
ProxyServer proxyServer = request.getProxyServer() != null ? request.getProxyServer() : config.getProxyServer();
boolean avoidProxy = ProxyUtils.avoidProxy( proxyServer, request );
if (!avoidProxy) {
if (proxyServer.getPrincipal() != null) {
Credentials defaultcreds = new UsernamePasswordCredentials(proxyServer.getPrincipal(), proxyServer.getPassword());
client.getState().setCredentials(new AuthScope(null, -1, AuthScope.ANY_REALM), defaultcreds);
}
ProxyHost proxyHost = proxyServer == null ? null : new ProxyHost(proxyServer.getHost(), proxyServer.getPort());
client.getHostConfiguration().setProxyHost(proxyHost);
}
method.setFollowRedirects(false);
if ((request.getCookies() != null) && !request.getCookies().isEmpty()) {
for (Cookie cookie : request.getCookies()) {
method.setRequestHeader("Cookie", AsyncHttpProviderUtils.encodeCookies(request.getCookies()));
}
}
if (request.getHeaders() != null) {
for (String name : request.getHeaders().keySet()) {
if (!"host".equalsIgnoreCase(name)) {
for (String value : request.getHeaders().get(name)) {
method.setRequestHeader(name, value);
}
}
}
}
if (request.getHeaders().getFirstValue("User-Agent") != null) {
method.setRequestHeader("User-Agent", request.getHeaders().getFirstValue("User-Agent"));
} else if (config.getUserAgent() != null) {
method.setRequestHeader("User-Agent", config.getUserAgent());
} else {
method.setRequestHeader("User-Agent", AsyncHttpProviderUtils.constructUserAgent(ApacheAsyncHttpProvider.class));
}
if (config.isCompressionEnabled()) {
Header acceptableEncodingHeader = method.getRequestHeader("Accept-Encoding");
if (acceptableEncodingHeader != null) {
String acceptableEncodings = acceptableEncodingHeader.getValue();
if (acceptableEncodings.indexOf("gzip") == -1) {
StringBuilder buf = new StringBuilder(acceptableEncodings);
if (buf.length() > 1) {
buf.append(",");
}
buf.append("gzip");
method.setRequestHeader("Accept-Encoding", buf.toString());
}
} else {
method.setRequestHeader("Accept-Encoding", "gzip");
}
}
if (request.getVirtualHost() != null) {
String vs = request.getVirtualHost();
int index = vs.indexOf(":");
if (index > 0) {
vs = vs.substring(0,index);
}
method.getParams().setVirtualHost(vs);
}
return method;
}
private final static int computeAndSetContentLength(Request request, HttpMethodBase m) {
int lenght = (int) request.getContentLength();
if (lenght == -1 && m.getRequestHeader("Content-Length") != null) {
lenght = Integer.valueOf(m.getRequestHeader("Content-Length").getValue());
}
if (lenght != -1) {
m.setRequestHeader("Content-Length", String.valueOf(lenght));
}
return lenght;
}
public class ApacheClientRunnable<T> implements Callable<T> {
private final AsyncHandler<T> asyncHandler;
private HttpMethodBase method;
private final ApacheResponseFuture<T> future;
private Request request;
private final HttpClient httpClient;
private int currentRedirectCount;
private AtomicBoolean isAuth = new AtomicBoolean(false);
private boolean terminate = true;
public ApacheClientRunnable(Request request, AsyncHandler<T> asyncHandler, HttpMethodBase method, ApacheResponseFuture<T> future, HttpClient httpClient) {
this.asyncHandler = asyncHandler;
this.method = method;
this.future = future;
this.request = request;
this.httpClient = httpClient;
}
public T call() {
terminate = true;
AsyncHandler.STATE state = AsyncHandler.STATE.ABORT;
try {
URI uri = null;
try {
uri = AsyncHttpProviderUtils.createUri(request.getRawUrl());
} catch (IllegalArgumentException u) {
uri = AsyncHttpProviderUtils.createUri(request.getUrl());
}
int delay = requestTimeout(config, future.getRequest().getPerRequestConfig());
if (delay != -1) {
ReaperFuture reaperFuture = new ReaperFuture(future);
Future scheduledFuture = config.reaper().scheduleAtFixedRate(reaperFuture, delay, 500, TimeUnit.MILLISECONDS);
reaperFuture.setScheduledFuture(scheduledFuture);
future.setReaperFuture(reaperFuture);
}
if (TransferCompletionHandler.class.isAssignableFrom(asyncHandler.getClass())) {
throw new IllegalStateException(TransferCompletionHandler.class.getName() + "not supported by this provider");
}
int statusCode = 200;
try {
statusCode = httpClient.executeMethod(method);
} catch (CircularRedirectException ex) {
// Quite ugly, but this is needed to unify
statusCode = 302;
currentRedirectCount = config.getMaxRedirects();
}
ApacheResponseStatus status = new ApacheResponseStatus(uri, method, ApacheAsyncHttpProvider.this);
FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(asyncHandler).request(request).responseStatus(status).build();
for (ResponseFilter asyncFilter : config.getResponseFilters()) {
fc = asyncFilter.filter(fc);
if (fc == null) {
throw new NullPointerException("FilterContext is null");
}
}
// The request has changed
if (fc.replayRequest()) {
request = fc.getRequest();
method = createMethod(httpClient, request);
terminate = false;
return call();
}
logger.debug("\n\nRequest {}\n\nResponse {}\n", request, method);
boolean redirectEnabled = (request.isRedirectEnabled() || config.isRedirectEnabled());
if (redirectEnabled && (statusCode == 302 || statusCode == 301)) {
isAuth.set(false);
if (currentRedirectCount++ < config.getMaxRedirects()) {
String location = method.getResponseHeader("Location").getValue();
URI rediUri = AsyncHttpProviderUtils.getRedirectUri(uri, location);
String newUrl = rediUri.toString();
if (!newUrl.equals(uri.toString())) {
RequestBuilder builder = new RequestBuilder(request);
logger.debug("Redirecting to {}", newUrl);
request = builder.setUrl(newUrl).build();
method = createMethod(httpClient, request);
terminate = false;
return call();
}
} else {
throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects());
}
}
state = asyncHandler.onStatusReceived(status);
if (state == AsyncHandler.STATE.CONTINUE) {
state = asyncHandler.onHeadersReceived(new ApacheResponseHeaders(uri, method, ApacheAsyncHttpProvider.this));
}
if (state == AsyncHandler.STATE.CONTINUE) {
InputStream is = method.getResponseBodyAsStream();
if (is != null) {
Header h = method.getResponseHeader("Content-Encoding");
if (h != null) {
String contentEncoding = h.getValue();
boolean isGZipped = contentEncoding == null ? false : "gzip".equalsIgnoreCase(contentEncoding);
if (isGZipped) {
is = new GZIPInputStream(is);
}
}
int byteToRead = (int) method.getResponseContentLength();
InputStream stream = is;
if (byteToRead <= 0) {
int[] lengthWrapper = new int[1];
byte[] bytes = AsyncHttpProviderUtils.readFully(is, lengthWrapper);
stream = new ByteArrayInputStream(bytes, 0, lengthWrapper[0]);
byteToRead = lengthWrapper[0];
}
if (byteToRead > 0) {
int minBytes = Math.min(8192, byteToRead);
byte[] bytes = new byte[minBytes];
int leftBytes = minBytes < 8192 ? minBytes : byteToRead;
int read = 0;
while (leftBytes > -1) {
try {
read = stream.read(bytes);
} catch (IOException ex) {
logger.warn("Connection closed", ex);
read = -1;
}
if (read == -1) {
break;
}
future.touch();
byte[] b = new byte[read];
System.arraycopy(bytes, 0, b, 0, read);
asyncHandler.onBodyPartReceived(new ApacheResponseBodyPart(uri, b, ApacheAsyncHttpProvider.this));
leftBytes -= read;
}
}
}
if (method.getName().equalsIgnoreCase("HEAD")) {
asyncHandler.onBodyPartReceived(new ApacheResponseBodyPart(uri, "".getBytes(), ApacheAsyncHttpProvider.this));
}
}
if (ProgressAsyncHandler.class.isAssignableFrom(asyncHandler.getClass())) {
ProgressAsyncHandler.class.cast(asyncHandler).onHeaderWriteCompleted();
ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteCompleted();
}
try {
return asyncHandler.onCompleted();
} catch (Throwable t) {
RuntimeException ex = new RuntimeException();
ex.initCause(t);
throw ex;
}
} catch (Throwable t) {
if (IOException.class.isAssignableFrom(t.getClass()) && config.getIOExceptionFilters().size() > 0) {
FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(asyncHandler)
.request(future.getRequest()).ioException(IOException.class.cast(t)).build();
try {
fc = handleIoException(fc);
} catch (FilterException e) {
if (config.getMaxTotalConnections() != -1) {
maxConnections.decrementAndGet();
}
future.done(null);
method.releaseConnection();
}
if (fc.replayRequest()) {
request = fc.getRequest();
return call();
}
}
if (method.isAborted()) {
return null;
}
logger.debug(t.getMessage(), t);
try {
future.abort(filterException(t));
} catch (Throwable t2) {
logger.error(t2.getMessage(), t2);
}
} finally {
if (terminate) {
if (config.getMaxTotalConnections() != -1) {
maxConnections.decrementAndGet();
}
future.done(null);
// Crappy Apache HttpClient who blocks forever here with large files.
config.executorService().submit(new Runnable() {
public void run() {
method.releaseConnection();
}
});
}
}
return null;
}
private Throwable filterException(Throwable t) {
if (UnknownHostException.class.isAssignableFrom(t.getClass())) {
t = new ConnectException(t.getMessage());
}
if (NoHttpResponseException.class.isAssignableFrom(t.getClass())) {
int responseTimeoutInMs = config.getRequestTimeoutInMs();
if (request.getPerRequestConfig() != null && request.getPerRequestConfig().getRequestTimeoutInMs() != -1) {
responseTimeoutInMs = request.getPerRequestConfig().getRequestTimeoutInMs();
}
t = new TimeoutException(String.format("No response received after %s", responseTimeoutInMs));
}
if (SSLHandshakeException.class.isAssignableFrom(t.getClass())) {
Throwable t2 = new ConnectException();
t2.initCause(t);
t = t2;
}
return t;
}
private FilterContext handleIoException(FilterContext fc) throws FilterException {
for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) {
fc = asyncFilter.filter(fc);
if (fc == null) {
throw new NullPointerException("FilterContext is null");
}
}
return fc;
}
}
private MultipartRequestEntity createMultipartRequestEntity(String charset, List<Part> params, HttpMethodParams methodParams) throws FileNotFoundException {
org.apache.commons.httpclient.methods.multipart.Part[] parts = new org.apache.commons.httpclient.methods.multipart.Part[params.size()];
int i = 0;
for (Part part : params) {
if (part instanceof StringPart) {
parts[i] = new org.apache.commons.httpclient.methods.multipart.StringPart(part.getName(),
((StringPart) part).getValue(),
charset);
} else if (part instanceof FilePart) {
parts[i] = new org.apache.commons.httpclient.methods.multipart.FilePart(part.getName(),
((FilePart) part).getFile(),
((FilePart) part).getMimeType(),
((FilePart) part).getCharSet());
} else if (part instanceof ByteArrayPart) {
PartSource source = new ByteArrayPartSource(((ByteArrayPart) part).getFileName(), ((ByteArrayPart) part).getData());
parts[i] = new org.apache.commons.httpclient.methods.multipart.FilePart(part.getName(),
source,
((ByteArrayPart) part).getMimeType(),
((ByteArrayPart) part).getCharSet());
} else if (part == null) {
throw new NullPointerException("Part cannot be null");
} else {
throw new IllegalArgumentException(String.format("Unsupported part type for multipart parameter %s",
part.getName()));
}
++i;
}
return new MultipartRequestEntity(parts, methodParams);
}
public class EntityWriterRequestEntity implements org.apache.commons.httpclient.methods.RequestEntity {
private Request.EntityWriter entityWriter;
private long contentLength;
public EntityWriterRequestEntity(Request.EntityWriter entityWriter, long contentLength) {
this.entityWriter = entityWriter;
this.contentLength = contentLength;
}
public long getContentLength() {
return contentLength;
}
public String getContentType() {
return null;
}
public boolean isRepeatable() {
return false;
}
public void writeRequest(OutputStream out) throws IOException {
entityWriter.writeEntity(out);
}
}
private static class TrustingSSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory delegate;
private TrustingSSLSocketFactory() {
try {
SSLContext sslcontext = SSLContext.getInstance("SSL");
sslcontext.init(null, new TrustManager[]{new TrustEveryoneTrustManager()}, new SecureRandom());
delegate = sslcontext.getSocketFactory();
} catch (KeyManagementException e) {
throw new IllegalStateException();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException();
}
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return delegate.createSocket(s, i);
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return delegate.createSocket(s, i, inetAddress, i1);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return delegate.createSocket(inetAddress, i);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return delegate.createSocket(inetAddress, i, inetAddress1, i1);
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
return delegate.createSocket(socket, s, i, b);
}
}
private static class TrustEveryoneTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
// do nothing
}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
// do nothing
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
private final class ReaperFuture implements Future, Runnable {
private Future scheduledFuture;
private ApacheResponseFuture<?> apacheResponseFuture;
public ReaperFuture(ApacheResponseFuture<?> apacheResponseFuture) {
this.apacheResponseFuture = apacheResponseFuture;
}
public void setScheduledFuture(Future scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
/**
* @Override
*/
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
//cleanup references to allow gc to reclaim memory independently
//of this Future lifecycle
this.apacheResponseFuture = null;
return this.scheduledFuture.cancel(mayInterruptIfRunning);
}
/**
* @Override
*/
public Object get() throws InterruptedException, ExecutionException {
return this.scheduledFuture.get();
}
/**
* @Override
*/
public Object get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
return this.scheduledFuture.get(timeout, unit);
}
/**
* @Override
*/
public boolean isCancelled() {
return this.scheduledFuture.isCancelled();
}
/**
* @Override
*/
public boolean isDone() {
return this.scheduledFuture.isDone();
}
/**
* @Override
*/
public synchronized void run() {
if (this.apacheResponseFuture != null && this.apacheResponseFuture.hasExpired()) {
logger.debug("Request Timeout expired for " + this.apacheResponseFuture);
int requestTimeout = config.getRequestTimeoutInMs();
PerRequestConfig p = this.apacheResponseFuture.getRequest().getPerRequestConfig();
if (p != null && p.getRequestTimeoutInMs() != -1) {
requestTimeout = p.getRequestTimeoutInMs();
}
apacheResponseFuture.abort(new TimeoutException(String.format("No response received after %s", requestTimeout)));
this.apacheResponseFuture = null;
}
}
}
protected static int requestTimeout(AsyncHttpClientConfig config, PerRequestConfig perRequestConfig) {
int result;
if (perRequestConfig != null) {
int prRequestTimeout = perRequestConfig.getRequestTimeoutInMs();
result = (prRequestTimeout != 0 ? prRequestTimeout : config.getRequestTimeoutInMs());
} else {
result = config.getRequestTimeoutInMs();
}
return result;
}
}