/*
* Copyright 2010 Ning, Inc.
*
* Ning 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.
*/
package com.ning.http.client.providers.netty;
import static com.ning.http.util.AsyncHttpProviderUtils.DEFAULT_CHARSET;
import static org.jboss.netty.channel.Channels.pipeline;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLEngine;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureProgressListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.DefaultChannelFuture;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.FileRegion;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultCookie;
import org.jboss.netty.handler.codec.http.DefaultHttpChunkTrailer;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
import org.jboss.netty.handler.codec.http.HttpClientCodec;
import org.jboss.netty.handler.codec.http.HttpContentCompressor;
import org.jboss.netty.handler.codec.http.HttpContentDecompressor;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.handler.stream.ChunkedFile;
import org.jboss.netty.handler.stream.ChunkedWriteHandler;
import com.ning.http.client.AsyncHandler;
import com.ning.http.client.AsyncHandler.STATE;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpProvider;
import com.ning.http.client.Body;
import com.ning.http.client.BodyGenerator;
import com.ning.http.client.ConnectionsPool;
import com.ning.http.client.Cookie;
import com.ning.http.client.FluentCaseInsensitiveStringsMap;
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.PerRequestConfig;
import com.ning.http.client.ProgressAsyncHandler;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.RandomAccessBody;
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.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.generators.InputStreamBodyGenerator;
import com.ning.http.client.listener.TransferCompletionHandler;
import com.ning.http.client.ntlm.NTLMEngine;
import com.ning.http.client.ntlm.NTLMEngineException;
import com.ning.http.client.providers.netty.spnego.SpnegoEngine;
import com.ning.http.client.websocket.WebSocketUpgradeHandler;
import com.ning.http.multipart.MultipartBody;
import com.ning.http.multipart.MultipartRequestEntity;
import com.ning.http.util.AsyncHttpProviderUtils;
import com.ning.http.util.AuthenticatorUtils;
import com.ning.http.util.CleanupChannelGroup;
import com.ning.http.util.ProxyUtils;
import com.ning.http.util.SslUtils;
import com.ning.http.util.UTF8UrlEncoder;
public class NettyAsyncHttpProvider extends SimpleChannelUpstreamHandler
implements AsyncHttpProvider {
private final static String WEBSOCKET_KEY = "Sec-WebSocket-Key";
private final static String HTTP_HANDLER = "httpHandler";
protected final static String SSL_HANDLER = "sslHandler";
private final static String HTTPS = "https";
private final static String HTTP = "http";
private static final String WEBSOCKET = "ws";
private final ClientBootstrap plainBootstrap;
private final ClientBootstrap secureBootstrap;
private final ClientBootstrap webSocketBootstrap;
private final static int MAX_BUFFERED_BYTES = 8192;
private final AsyncHttpClientConfig config;
private final AtomicBoolean isClose = new AtomicBoolean(false);
private final ClientSocketChannelFactory socketChannelFactory;
private final ChannelGroup openChannels = new CleanupChannelGroup(
"asyncHttpClient") {
@Override
public boolean remove(final Object o) {
final boolean removed = super.remove(o);
if (removed && trackConnections) {
freeConnections.release();
}
return removed;
}
};
private final ConnectionsPool<String, Channel> connectionsPool;
private Semaphore freeConnections = null;
private final NettyAsyncHttpProviderConfig asyncHttpProviderConfig;
private boolean executeConnectAsync = true;
public static final ThreadLocal<Boolean> IN_IO_THREAD = new ThreadLocalBoolean();
private final boolean trackConnections;
private final boolean useRawUrl;
private final static NTLMEngine ntlmEngine = new NTLMEngine();
private final static SpnegoEngine spnegoEngine = new SpnegoEngine();
private final Protocol httpProtocol = new HttpProtocol();
public NettyAsyncHttpProvider(final AsyncHttpClientConfig config) {
if (config.getAsyncHttpProviderConfig() != null
&& NettyAsyncHttpProviderConfig.class.isAssignableFrom(config
.getAsyncHttpProviderConfig().getClass())) {
asyncHttpProviderConfig = NettyAsyncHttpProviderConfig.class
.cast(config.getAsyncHttpProviderConfig());
} else {
asyncHttpProviderConfig = new NettyAsyncHttpProviderConfig();
}
if (asyncHttpProviderConfig
.getProperty(NettyAsyncHttpProviderConfig.USE_BLOCKING_IO) != null) {
socketChannelFactory = new OioClientSocketChannelFactory(
config.executorService());
} else {
ExecutorService e;
final Object o = asyncHttpProviderConfig
.getProperty(NettyAsyncHttpProviderConfig.BOSS_EXECUTOR_SERVICE);
if (o != null
&& ExecutorService.class.isAssignableFrom(o.getClass())) {
e = ExecutorService.class.cast(o);
} else {
e = Executors.newCachedThreadPool();
}
final int numWorkers = config.getIoThreadMultiplier()
* Runtime.getRuntime().availableProcessors();
socketChannelFactory = new NioClientSocketChannelFactory(e,
config.executorService(), numWorkers);
}
plainBootstrap = new ClientBootstrap(socketChannelFactory);
secureBootstrap = new ClientBootstrap(socketChannelFactory);
webSocketBootstrap = new ClientBootstrap(socketChannelFactory);
configureNetty();
this.config = config;
// This is dangerous as we can't catch a wrong typed ConnectionsPool
ConnectionsPool<String, Channel> cp = (ConnectionsPool<String, Channel>) config
.getConnectionsPool();
if (cp == null && config.getAllowPoolingConnection()) {
cp = new NettyConnectionsPool(this);
} else if (cp == null) {
cp = new NonConnectionsPool();
}
this.connectionsPool = cp;
if (config.getMaxTotalConnections() != -1) {
trackConnections = true;
freeConnections = new Semaphore(config.getMaxTotalConnections());
} else {
trackConnections = false;
}
useRawUrl = config.isUseRawUrl();
}
@Override
public String toString() {
return String
.format("NettyAsyncHttpProvider:\n\t- maxConnections: %d\n\t- openChannels: %s\n\t- connectionPools: %s",
config.getMaxTotalConnections()
- freeConnections.availablePermits(),
openChannels.toString(), connectionsPool.toString());
}
void configureNetty() {
if (asyncHttpProviderConfig != null) {
for (final Entry<String, Object> entry : asyncHttpProviderConfig
.propertiesSet()) {
plainBootstrap.setOption(entry.getKey(), entry.getValue());
}
}
plainBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/* @Override */
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = pipeline();
pipeline.addLast(HTTP_HANDLER, new HttpClientCodec());
if (config.getRequestCompressionLevel() > 0) {
pipeline.addLast("deflater", new HttpContentCompressor(
config.getRequestCompressionLevel()));
}
if (config.isCompressionEnabled()) {
pipeline.addLast("inflater", new HttpContentDecompressor());
}
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this);
return pipeline;
}
});
DefaultChannelFuture.setUseDeadLockChecker(false);
if (asyncHttpProviderConfig != null) {
final Object value = asyncHttpProviderConfig
.getProperty(NettyAsyncHttpProviderConfig.EXECUTE_ASYNC_CONNECT);
if (value != null
&& Boolean.class.isAssignableFrom(value.getClass())) {
executeConnectAsync = Boolean.class.cast(value);
} else if (asyncHttpProviderConfig
.getProperty(NettyAsyncHttpProviderConfig.DISABLE_NESTED_REQUEST) != null) {
DefaultChannelFuture.setUseDeadLockChecker(true);
}
}
webSocketBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/* @Override */
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = pipeline();
pipeline.addLast("ws-decoder", new HttpResponseDecoder());
pipeline.addLast("ws-encoder", new HttpRequestEncoder());
pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this);
return pipeline;
}
});
}
void constructSSLPipeline(final NettyConnectListener<?> cl) {
secureBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/* @Override */
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = pipeline();
try {
pipeline.addLast(SSL_HANDLER, new SslHandler(
createSSLEngine()));
} catch (final Throwable ex) {
abort(cl.future(), ex);
}
pipeline.addLast(HTTP_HANDLER, new HttpClientCodec());
if (config.isCompressionEnabled()) {
pipeline.addLast("inflater", new HttpContentDecompressor());
}
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this);
return pipeline;
}
});
if (asyncHttpProviderConfig != null) {
for (final Entry<String, Object> entry : asyncHttpProviderConfig
.propertiesSet()) {
secureBootstrap.setOption(entry.getKey(), entry.getValue());
}
}
}
private Channel lookupInCache(final URI uri) {
final Channel channel = connectionsPool.poll(AsyncHttpProviderUtils
.getBaseUrl(uri));
if (channel != null) {
try {
// Always make sure the channel who got cached support the
// proper protocol. It could
// only occurs when a HttpMethod.CONNECT is used agains a proxy
// that require upgrading from http to
// https.
return verifyChannelPipeline(channel, uri.getScheme());
} catch (final Exception ex) {
ex.printStackTrace();
}
}
return null;
}
private SSLEngine createSSLEngine() throws IOException,
GeneralSecurityException {
SSLEngine sslEngine = config.getSSLEngineFactory().newSSLEngine();
if (sslEngine == null) {
sslEngine = SslUtils.getSSLEngine();
}
return sslEngine;
}
private Channel verifyChannelPipeline(final Channel channel,
final String scheme) throws IOException, GeneralSecurityException {
if (channel.getPipeline().get(SSL_HANDLER) != null
&& HTTP.equalsIgnoreCase(scheme)) {
channel.getPipeline().remove(SSL_HANDLER);
} else if (channel.getPipeline().get(HTTP_HANDLER) != null
&& HTTP.equalsIgnoreCase(scheme)) {
return channel;
} else if (channel.getPipeline().get(SSL_HANDLER) == null
&& HTTPS.equalsIgnoreCase(scheme)) {
channel.getPipeline().addFirst(SSL_HANDLER,
new SslHandler(createSSLEngine()));
}
return channel;
}
protected final <T> void writeRequest(final Channel channel,
final AsyncHttpClientConfig config,
final NettyResponseFuture<T> future, final HttpRequest nettyRequest) {
try {
/**
* If the channel is dead because it was pooled and the remote
* server decided to close it, we just let it go and the
* closeChannel do it's work.
*/
if (!channel.isOpen() || !channel.isConnected()) {
return;
}
Body body = null;
if (!future.getNettyRequest().getMethod()
.equals(HttpMethod.CONNECT)) {
final BodyGenerator bg = future.getRequest().getBodyGenerator();
if (bg != null) {
// Netty issue with chunking.
if (InputStreamBodyGenerator.class.isAssignableFrom(bg
.getClass())) {
InputStreamBodyGenerator.class.cast(bg)
.patchNettyChunkingIssue(true);
}
try {
body = bg.createBody();
} catch (final IOException ex) {
throw new IllegalStateException(ex);
}
final long length = body.getContentLength();
if (length >= 0) {
nettyRequest.setHeader(
HttpHeaders.Names.CONTENT_LENGTH, length);
} else {
nettyRequest.setHeader(
HttpHeaders.Names.TRANSFER_ENCODING,
HttpHeaders.Values.CHUNKED);
}
} else {
body = null;
}
}
if (TransferCompletionHandler.class.isAssignableFrom(future
.getAsyncHandler().getClass())) {
final FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap();
for (final String s : future.getNettyRequest().getHeaderNames()) {
for (final String header : future.getNettyRequest()
.getHeaders(s)) {
h.add(s, header);
}
}
TransferCompletionHandler.class.cast(future.getAsyncHandler())
.transferAdapter(
new NettyTransferAdapter(h, nettyRequest
.getContent(), future.getRequest()
.getFile()));
}
// Leave it to true.
if (future.getAndSetWriteHeaders(true)) {
try {
channel.write(nettyRequest).addListener(
new ProgressListener(true,
future.getAsyncHandler(), future));
} catch (final Throwable cause) {
try {
channel.close();
} catch (final RuntimeException ex) {
}
return;
}
}
if (future.getAndSetWriteBody(true)) {
if (!future.getNettyRequest().getMethod()
.equals(HttpMethod.CONNECT)) {
if (future.getRequest().getFile() != null) {
final File file = future.getRequest().getFile();
long fileLength = 0;
final RandomAccessFile raf = new RandomAccessFile(file,
"r");
try {
fileLength = raf.length();
ChannelFuture writeFuture;
if (channel.getPipeline().get(SslHandler.class) != null) {
writeFuture = channel.write(new ChunkedFile(
raf, 0, fileLength, 8192));
} else {
final FileRegion region = new OptimizedFileRegion(
raf, 0, fileLength);
writeFuture = channel.write(region);
}
writeFuture.addListener(new ProgressListener(false,
future.getAsyncHandler(), future));
} catch (final IOException ex) {
if (raf != null) {
try {
raf.close();
} catch (final IOException e) {
}
}
throw ex;
}
} else if (body != null
|| future.getRequest().getParts() != null) {
/**
* TODO: AHC-78: SSL + zero copy isn't supported by the
* MultiPart class and pretty complex to implements.
*/
if (future.getRequest().getParts() != null) {
final String boundary = future.getNettyRequest()
.getHeader("Content-Type");
final String length = future.getNettyRequest()
.getHeader("Content-Length");
body = new MultipartBody(future.getRequest()
.getParts(), boundary, length);
}
ChannelFuture writeFuture;
if (channel.getPipeline().get(SslHandler.class) == null
&& (body instanceof RandomAccessBody)) {
final BodyFileRegion bodyFileRegion = new BodyFileRegion(
(RandomAccessBody) body);
writeFuture = channel.write(bodyFileRegion);
} else {
final BodyChunkedInput bodyChunkedInput = new BodyChunkedInput(
body);
writeFuture = channel.write(bodyChunkedInput);
}
final Body b = body;
writeFuture.addListener(new ProgressListener(false,
future.getAsyncHandler(), future) {
@Override
public void operationComplete(final ChannelFuture cf) {
try {
b.close();
} catch (final IOException e) {
}
super.operationComplete(cf);
}
});
}
}
}
} catch (final Throwable ioe) {
try {
channel.close();
} catch (final RuntimeException ex) {
}
}
try {
future.touch();
final int delay = requestTimeout(config, future.getRequest()
.getPerRequestConfig());
if (delay != -1 && !future.isDone() && !future.isCancelled()) {
final ReaperFuture reaperFuture = new ReaperFuture(channel,
future);
final Future scheduledFuture = config.reaper()
.scheduleAtFixedRate(reaperFuture, 0, delay,
TimeUnit.MILLISECONDS);
reaperFuture.setScheduledFuture(scheduledFuture);
future.setReaperFuture(reaperFuture);
}
} catch (final RejectedExecutionException ex) {
abort(future, ex);
}
}
private static boolean isProxyServer(final AsyncHttpClientConfig config,
final Request request) {
return request.getProxyServer() != null
|| config.getProxyServer() != null;
}
protected final static HttpRequest buildRequest(
final AsyncHttpClientConfig config, final Request request,
final URI uri, final boolean allowConnect,
final ChannelBuffer buffer) throws IOException {
String method = request.getMethod();
if (allowConnect
&& (isProxyServer(config, request) && HTTPS
.equalsIgnoreCase(uri.getScheme()))) {
method = HttpMethod.CONNECT.toString();
}
return construct(config, request, new HttpMethod(method), uri, buffer);
}
@SuppressWarnings("deprecation")
private static HttpRequest construct(final AsyncHttpClientConfig config,
final Request request, final HttpMethod m, final URI uri,
final ChannelBuffer buffer) throws IOException {
String host = AsyncHttpProviderUtils.getHost(uri);
if (request.getVirtualHost() != null) {
host = request.getVirtualHost();
}
HttpRequest nettyRequest;
if (m.equals(HttpMethod.CONNECT)) {
nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_0, m,
AsyncHttpProviderUtils.getAuthority(uri));
} else {
StringBuilder path = null;
if (isProxyServer(config, request))
path = new StringBuilder(uri.toString());
else {
path = new StringBuilder(uri.getRawPath());
if (uri.getQuery() != null) {
path.append("?").append(uri.getRawQuery());
}
}
nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, m,
path.toString());
}
final boolean webSocket = uri.getScheme().equalsIgnoreCase(WEBSOCKET);
if (webSocket) {
nettyRequest.addHeader(HttpHeaders.Names.UPGRADE,
HttpHeaders.Values.WEBSOCKET);
nettyRequest.addHeader(HttpHeaders.Names.CONNECTION,
HttpHeaders.Values.UPGRADE);
nettyRequest.addHeader("Sec-WebSocket-Origin",
"http://" + uri.getHost());
nettyRequest.addHeader(WEBSOCKET_KEY, WebSocketUtil.getKey());
nettyRequest.addHeader("Sec-WebSocket-Version", "8");
}
if (host != null) {
if (uri.getPort() == -1) {
nettyRequest.setHeader(HttpHeaders.Names.HOST, host);
} else if (request.getVirtualHost() != null) {
nettyRequest.setHeader(HttpHeaders.Names.HOST, host);
} else {
nettyRequest.setHeader(HttpHeaders.Names.HOST,
host + ":" + uri.getPort());
}
} else {
host = "127.0.0.1";
}
if (!m.equals(HttpMethod.CONNECT)) {
final FluentCaseInsensitiveStringsMap h = request.getHeaders();
if (h != null) {
for (final String name : h.keySet()) {
if (!"host".equalsIgnoreCase(name)) {
for (final String value : h.get(name)) {
nettyRequest.addHeader(name, value);
}
}
}
}
if (config.isCompressionEnabled()) {
nettyRequest.setHeader(HttpHeaders.Names.ACCEPT_ENCODING,
HttpHeaders.Values.GZIP);
}
} else {
final List<String> auth = request.getHeaders().get(
HttpHeaders.Names.PROXY_AUTHORIZATION);
if (auth != null && auth.size() > 0
&& auth.get(0).startsWith("NTLM")) {
nettyRequest.addHeader(HttpHeaders.Names.PROXY_AUTHORIZATION,
auth.get(0));
}
}
final ProxyServer proxyServer = request.getProxyServer() != null ? request
.getProxyServer() : config.getProxyServer();
final Realm realm = request.getRealm() != null ? request.getRealm()
: config.getRealm();
if (realm != null && realm.getUsePreemptiveAuth()) {
String domain = realm.getNtlmDomain();
if (proxyServer != null && proxyServer.getNtlmDomain() != null) {
domain = proxyServer.getNtlmDomain();
}
final String authHost = realm.getNtlmHost();
if (proxyServer != null && proxyServer.getHost() != null) {
host = proxyServer.getHost();
}
switch (realm.getAuthScheme()) {
case BASIC:
nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION,
AuthenticatorUtils.computeBasicAuthentication(realm));
break;
case DIGEST:
if (realm.getNonce() != null && !realm.getNonce().equals("")) {
try {
nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION,
AuthenticatorUtils
.computeDigestAuthentication(realm));
} catch (final NoSuchAlgorithmException e) {
throw new SecurityException(e);
}
}
break;
case NTLM:
try {
nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION,
ntlmEngine.generateType1Msg("NTLM " + domain,
authHost));
} catch (final NTLMEngineException e) {
final IOException ie = new IOException();
ie.initCause(e);
throw ie;
}
break;
case KERBEROS:
case SPNEGO:
String challengeHeader = null;
final String server = proxyServer == null ? host : proxyServer
.getHost();
try {
challengeHeader = spnegoEngine.generateToken(server);
} catch (final Throwable e) {
final IOException ie = new IOException();
ie.initCause(e);
throw ie;
}
nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION,
"Negotiate " + challengeHeader);
break;
case NONE:
break;
default:
throw new IllegalStateException(String.format(
"Invalid Authentication %s", realm.toString()));
}
}
if (!webSocket
&& !request.getHeaders().containsKey(
HttpHeaders.Names.CONNECTION)) {
nettyRequest.setHeader(HttpHeaders.Names.CONNECTION, "keep-alive");
}
final boolean avoidProxy = ProxyUtils.avoidProxy(proxyServer, request);
if (!avoidProxy) {
if (!request.getHeaders().containsKey("Proxy-Connection")) {
nettyRequest.setHeader("Proxy-Connection", "keep-alive");
}
if (proxyServer.getPrincipal() != null) {
if (proxyServer.getNtlmDomain() != null
&& proxyServer.getNtlmDomain().length() > 0) {
final List<String> auth = request.getHeaders().get(
HttpHeaders.Names.PROXY_AUTHORIZATION);
if (!(auth != null && auth.size() > 0 && auth.get(0)
.startsWith("NTLM"))) {
try {
final String msg = ntlmEngine.generateType1Msg(
proxyServer.getNtlmDomain(),
proxyServer.getHost());
nettyRequest.setHeader(
HttpHeaders.Names.PROXY_AUTHORIZATION,
"NTLM " + msg);
} catch (final NTLMEngineException e) {
final IOException ie = new IOException();
ie.initCause(e);
throw ie;
}
}
} else {
nettyRequest.setHeader(
HttpHeaders.Names.PROXY_AUTHORIZATION,
AuthenticatorUtils
.computeBasicAuthentication(proxyServer));
}
}
}
// Add default accept headers.
if (request.getHeaders().getFirstValue("Accept") == null) {
nettyRequest.setHeader(HttpHeaders.Names.ACCEPT, "*/*");
}
if (request.getHeaders().getFirstValue("User-Agent") != null) {
nettyRequest.setHeader("User-Agent", request.getHeaders()
.getFirstValue("User-Agent"));
} else if (config.getUserAgent() != null) {
nettyRequest.setHeader("User-Agent", config.getUserAgent());
} else {
nettyRequest.setHeader("User-Agent", AsyncHttpProviderUtils
.constructUserAgent(NettyAsyncHttpProvider.class));
}
if (!m.equals(HttpMethod.CONNECT)) {
if (request.getCookies() != null && !request.getCookies().isEmpty()) {
final CookieEncoder httpCookieEncoder = new CookieEncoder(false);
final Iterator<Cookie> ic = request.getCookies().iterator();
Cookie c;
org.jboss.netty.handler.codec.http.Cookie cookie;
while (ic.hasNext()) {
c = ic.next();
cookie = new DefaultCookie(c.getName(), c.getValue());
cookie.setPath(c.getPath());
cookie.setMaxAge(c.getMaxAge());
cookie.setDomain(c.getDomain());
httpCookieEncoder.addCookie(cookie);
}
nettyRequest.setHeader(HttpHeaders.Names.COOKIE,
httpCookieEncoder.encode());
}
final String reqType = request.getMethod();
if (!"GET".equals(reqType) && !"HEAD".equals(reqType)
&& !"OPTION".equals(reqType) && !"TRACE".equals(reqType)) {
final String bodyCharset = request.getBodyEncoding() == null ? DEFAULT_CHARSET
: request.getBodyEncoding();
// We already have processed the body.
if (buffer != null && buffer.writerIndex() != 0) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
buffer.writerIndex());
nettyRequest.setContent(buffer);
} else if (request.getByteData() != null) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(request.getByteData().length));
nettyRequest.setContent(ChannelBuffers
.wrappedBuffer(request.getByteData()));
} else if (request.getStringData() != null) {
nettyRequest.setHeader(
HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(request.getStringData().getBytes(
bodyCharset).length));
nettyRequest.setContent(ChannelBuffers
.wrappedBuffer(request.getStringData().getBytes(
bodyCharset)));
} else if (request.getStreamData() != null) {
final int[] lengthWrapper = new int[1];
final byte[] bytes = AsyncHttpProviderUtils.readFully(
request.getStreamData(), lengthWrapper);
final int length = lengthWrapper[0];
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(length));
nettyRequest.setContent(ChannelBuffers.wrappedBuffer(bytes,
0, length));
} else if (request.getParams() != null
&& !request.getParams().isEmpty()) {
final StringBuilder sb = new StringBuilder();
for (final 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);
}
}
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(sb.length()));
nettyRequest.setContent(ChannelBuffers.wrappedBuffer(sb
.toString().getBytes(bodyCharset)));
if (!request.getHeaders().containsKey(
HttpHeaders.Names.CONTENT_TYPE)) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE,
"application/x-www-form-urlencoded");
}
} else if (request.getParts() != null) {
int lenght = computeAndSetContentLength(request,
nettyRequest);
if (lenght == -1) {
lenght = MAX_BUFFERED_BYTES;
}
final MultipartRequestEntity mre = AsyncHttpProviderUtils
.createMultipartRequestEntity(request.getParts(),
request.getParams());
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE,
mre.getContentType());
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(mre.getContentLength()));
/**
* TODO: AHC-78: SSL + zero copy isn't supported by the
* MultiPart class and pretty complex to implements.
*/
if (uri.toString().startsWith(HTTPS)) {
final ChannelBuffer b = ChannelBuffers
.dynamicBuffer(lenght);
mre.writeRequest(new ChannelBufferOutputStream(b));
nettyRequest.setContent(b);
}
} else if (request.getEntityWriter() != null) {
int lenght = computeAndSetContentLength(request,
nettyRequest);
if (lenght == -1) {
lenght = MAX_BUFFERED_BYTES;
}
final ChannelBuffer b = ChannelBuffers
.dynamicBuffer(lenght);
request.getEntityWriter().writeEntity(
new ChannelBufferOutputStream(b));
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
b.writerIndex());
nettyRequest.setContent(b);
} else if (request.getFile() != null) {
final File file = request.getFile();
if (!file.isFile()) {
throw new IOException(String.format(
"File %s is not a file or doesn't exist",
file.getAbsolutePath()));
}
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
file.length());
}
}
}
return nettyRequest;
}
@Override
public void close() {
isClose.set(true);
try {
connectionsPool.destroy();
openChannels.close();
for (final Channel channel : openChannels) {
final ChannelHandlerContext ctx = channel.getPipeline()
.getContext(NettyAsyncHttpProvider.class);
if (ctx.getAttachment() instanceof NettyResponseFuture<?>) {
final NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx
.getAttachment();
future.setReaperFuture(null);
}
}
config.executorService().shutdown();
config.reaper().shutdown();
socketChannelFactory.releaseExternalResources();
plainBootstrap.releaseExternalResources();
secureBootstrap.releaseExternalResources();
webSocketBootstrap.releaseExternalResources();
} catch (final Throwable t) {
}
}
/* @Override */
@Override
public Response prepareResponse(final HttpResponseStatus status,
final HttpResponseHeaders headers,
final Collection<HttpResponseBodyPart> bodyParts) {
return new NettyResponse(status, headers, bodyParts);
}
/* @Override */
@Override
public <T> ListenableFuture<T> execute(final Request request,
final AsyncHandler<T> asyncHandler) throws IOException {
return doConnect(request, asyncHandler, null, true,
executeConnectAsync, false);
}
private <T> void execute(final Request request,
final NettyResponseFuture<T> f, final boolean useCache,
final boolean asyncConnect) throws IOException {
doConnect(request, f.getAsyncHandler(), f, useCache, asyncConnect,
false);
}
private <T> void execute(final Request request,
final NettyResponseFuture<T> f, final boolean useCache,
final boolean asyncConnect, final boolean reclaimCache)
throws IOException {
doConnect(request, f.getAsyncHandler(), f, useCache, asyncConnect,
reclaimCache);
}
private <T> ListenableFuture<T> doConnect(final Request request,
final AsyncHandler<T> asyncHandler, NettyResponseFuture<T> f,
final boolean useCache, final boolean asyncConnect,
final boolean reclaimCache) throws IOException {
if (isClose.get()) {
throw new IOException("Closed");
}
if (request.getUrl().startsWith(WEBSOCKET)
&& !validateWebSocketRequest(request, asyncHandler)) {
throw new IOException("WebSocket method must be a GET");
}
final ProxyServer proxyServer = request.getProxyServer() != null ? request
.getProxyServer() : config.getProxyServer();
String requestUrl;
if (useRawUrl) {
requestUrl = request.getRawUrl();
} else {
requestUrl = request.getUrl();
}
final URI uri = AsyncHttpProviderUtils.createUri(requestUrl);
Channel channel = null;
if (useCache) {
if (f != null && f.reuseChannel() && f.channel() != null) {
channel = f.channel();
} else {
channel = lookupInCache(uri);
}
}
ChannelBuffer bufferedBytes = null;
if (f != null
&& f.getRequest().getFile() == null
&& !f.getNettyRequest().getMethod().getName()
.equals(HttpMethod.CONNECT.getName())) {
bufferedBytes = f.getNettyRequest().getContent();
}
final boolean useSSl = uri.getScheme().compareToIgnoreCase(HTTPS) == 0
&& proxyServer == null;
if (channel != null && channel.isOpen() && channel.isConnected()) {
HttpRequest nettyRequest = buildRequest(config, request, uri,
f == null ? false : f.isConnectAllowed(), bufferedBytes);
if (f == null) {
f = newFuture(uri, request, asyncHandler, nettyRequest, config,
this);
} else {
nettyRequest = buildRequest(config, request, uri,
f.isConnectAllowed(), bufferedBytes);
f.setNettyRequest(nettyRequest);
}
f.setState(NettyResponseFuture.STATE.POOLED);
f.attachChannel(channel, false);
channel.getPipeline().getContext(NettyAsyncHttpProvider.class)
.setAttachment(f);
try {
writeRequest(channel, config, f, nettyRequest);
} catch (final Exception ex) {
if (useSSl && ex.getMessage() != null
&& ex.getMessage().contains("SSLEngine")) {
f = null;
} else {
try {
asyncHandler.onThrowable(ex);
} catch (final Throwable t) {
}
final IOException ioe = new IOException(ex.getMessage());
ioe.initCause(ex);
throw ioe;
}
}
return f;
}
// Do not throw an exception when we need an extra connection for a
// redirect.
if (!reclaimCache && !connectionsPool.canCacheConnection()) {
final IOException ex = new IOException(String.format(
"Too many connections %s", config.getMaxTotalConnections()));
try {
asyncHandler.onThrowable(ex);
} catch (final Throwable t) {
}
throw ex;
}
boolean acquiredConnection = false;
if (trackConnections) {
if (!reclaimCache) {
if (!freeConnections.tryAcquire()) {
final IOException ex = new IOException(String.format(
"Too many connections %s",
config.getMaxTotalConnections()));
try {
asyncHandler.onThrowable(ex);
} catch (final Throwable t) {
}
throw ex;
} else {
acquiredConnection = true;
}
}
}
final NettyConnectListener<T> c = new NettyConnectListener.Builder<T>(
config, request, asyncHandler, f, this, bufferedBytes)
.build(uri);
final boolean avoidProxy = ProxyUtils.avoidProxy(proxyServer,
uri.getHost());
if (useSSl) {
constructSSLPipeline(c);
}
ChannelFuture channelFuture;
final ClientBootstrap bootstrap = request.getUrl()
.startsWith(WEBSOCKET) ? webSocketBootstrap
: (useSSl ? secureBootstrap : plainBootstrap);
bootstrap.setOption("connectTimeoutMillis",
config.getConnectionTimeoutInMs());
// Do no enable this with win.
if (System.getProperty("os.name").toLowerCase().indexOf("win") == -1) {
bootstrap.setOption("reuseAddress", asyncHttpProviderConfig
.getProperty(NettyAsyncHttpProviderConfig.REUSE_ADDRESS));
}
try {
if (request.getInetAddress() != null) {
channelFuture = bootstrap
.connect(new InetSocketAddress(
request.getInetAddress(),
AsyncHttpProviderUtils.getPort(uri)));
} else if (proxyServer == null || avoidProxy) {
channelFuture = bootstrap.connect(new InetSocketAddress(
AsyncHttpProviderUtils.getHost(uri),
AsyncHttpProviderUtils.getPort(uri)));
} else {
channelFuture = bootstrap.connect(new InetSocketAddress(
proxyServer.getHost(), proxyServer.getPort()));
}
} catch (final Throwable t) {
if (acquiredConnection) {
freeConnections.release();
}
abort(c.future(), t.getCause() == null ? t : t.getCause());
return c.future();
}
boolean directInvokation = true;
if (IN_IO_THREAD.get() && DefaultChannelFuture.isUseDeadLockChecker()) {
directInvokation = false;
}
if (directInvokation && !asyncConnect && request.getFile() == null) {
final int timeOut = config.getConnectionTimeoutInMs() > 0 ? config
.getConnectionTimeoutInMs() : Integer.MAX_VALUE;
if (!channelFuture.awaitUninterruptibly(timeOut,
TimeUnit.MILLISECONDS)) {
if (acquiredConnection) {
freeConnections.release();
}
channelFuture.cancel();
abort(c.future(),
new ConnectException(String.format(
"Connect operation to %s timeout %s", uri,
timeOut)));
}
try {
c.operationComplete(channelFuture);
} catch (final Exception e) {
if (acquiredConnection) {
freeConnections.release();
}
final IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
try {
asyncHandler.onThrowable(ioe);
} catch (final Throwable t) {
}
throw ioe;
}
} else {
channelFuture.addListener(c);
}
if (!c.future().isCancelled() || !c.future().isDone()) {
openChannels.add(channelFuture.getChannel());
c.future().attachChannel(channelFuture.getChannel(), false);
}
return c.future();
}
protected static int requestTimeout(final AsyncHttpClientConfig config,
final PerRequestConfig perRequestConfig) {
int result;
if (perRequestConfig != null) {
final int prRequestTimeout = perRequestConfig
.getRequestTimeoutInMs();
result = (prRequestTimeout != 0 ? prRequestTimeout : config
.getRequestTimeoutInMs());
} else {
result = config.getRequestTimeoutInMs();
}
return result;
}
private void closeChannel(final ChannelHandlerContext ctx) {
connectionsPool.removeAll(ctx.getChannel());
finishChannel(ctx);
}
private void finishChannel(final ChannelHandlerContext ctx) {
ctx.setAttachment(new DiscardEvent());
// The channel may have already been removed if a timeout occurred, and
// this method may be called just after.
if (ctx.getChannel() == null) {
return;
}
try {
ctx.getChannel().close();
} catch (final Throwable t) {
}
if (ctx.getChannel() != null) {
openChannels.remove(ctx.getChannel());
}
}
@Override
public void messageReceived(final ChannelHandlerContext ctx,
final MessageEvent e) throws Exception {
// call super to reset the read timeout
super.messageReceived(ctx, e);
IN_IO_THREAD.set(Boolean.TRUE);
if (ctx.getAttachment() == null) {
}
if (ctx.getAttachment() instanceof DiscardEvent) {
return;
} else if (ctx.getAttachment() instanceof AsyncCallable) {
if (e.getMessage() instanceof HttpChunk) {
final HttpChunk chunk = (HttpChunk) e.getMessage();
if (chunk.isLast()) {
final AsyncCallable ac = (AsyncCallable) ctx
.getAttachment();
ac.call();
} else {
return;
}
} else {
final AsyncCallable ac = (AsyncCallable) ctx.getAttachment();
ac.call();
}
ctx.setAttachment(new DiscardEvent());
return;
} else if (!(ctx.getAttachment() instanceof NettyResponseFuture<?>)) {
try {
ctx.getChannel().close();
} catch (final Throwable t) {
}
return;
}
final Protocol p = httpProtocol;
p.handle(ctx, e);
}
private Realm kerberosChallenge(final List<String> proxyAuth,
final Request request, final ProxyServer proxyServer,
final FluentCaseInsensitiveStringsMap headers, final Realm realm,
final NettyResponseFuture<?> future) throws NTLMEngineException {
final URI uri = URI.create(request.getUrl());
final String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils
.getHost(uri) : request.getVirtualHost();
final String server = proxyServer == null ? host : proxyServer
.getHost();
try {
final String challengeHeader = spnegoEngine.generateToken(server);
headers.remove(HttpHeaders.Names.AUTHORIZATION);
headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate "
+ challengeHeader);
Realm.RealmBuilder realmBuilder;
if (realm != null) {
realmBuilder = new Realm.RealmBuilder().clone(realm);
} else {
realmBuilder = new Realm.RealmBuilder();
}
return realmBuilder.setUri(uri.getPath())
.setMethodName(request.getMethod())
.setScheme(Realm.AuthScheme.KERBEROS).build();
} catch (final Throwable throwable) {
if (proxyAuth.contains("NTLM")) {
return ntlmChallenge(proxyAuth, request, proxyServer, headers,
realm, future);
}
abort(future, throwable);
return null;
}
}
private Realm ntlmChallenge(final List<String> wwwAuth,
final Request request, final ProxyServer proxyServer,
final FluentCaseInsensitiveStringsMap headers, final Realm realm,
final NettyResponseFuture<?> future) throws NTLMEngineException {
final boolean useRealm = (proxyServer == null && realm != null);
final String ntlmDomain = useRealm ? realm.getNtlmDomain()
: proxyServer.getNtlmDomain();
final String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer
.getHost();
final String principal = useRealm ? realm.getPrincipal() : proxyServer
.getPrincipal();
final String password = useRealm ? realm.getPassword() : proxyServer
.getPassword();
Realm newRealm;
if (realm != null && !realm.isNtlmMessageType2Received()) {
final String challengeHeader = ntlmEngine.generateType1Msg(
ntlmDomain, ntlmHost);
headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM "
+ challengeHeader);
newRealm = new Realm.RealmBuilder().clone(realm)
.setScheme(realm.getAuthScheme())
.setUri(URI.create(request.getUrl()).getPath())
.setMethodName(request.getMethod())
.setNtlmMessageType2Received(true).build();
future.getAndSetAuth(false);
} else {
headers.remove(HttpHeaders.Names.AUTHORIZATION);
if (wwwAuth.get(0).startsWith("NTLM ")) {
final String serverChallenge = wwwAuth.get(0).trim()
.substring("NTLM ".length());
final String challengeHeader = ntlmEngine.generateType3Msg(
principal, password, ntlmDomain, ntlmHost,
serverChallenge);
headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM "
+ challengeHeader);
}
Realm.RealmBuilder realmBuilder;
Realm.AuthScheme authScheme;
if (realm != null) {
realmBuilder = new Realm.RealmBuilder().clone(realm);
authScheme = realm.getAuthScheme();
} else {
realmBuilder = new Realm.RealmBuilder();
authScheme = Realm.AuthScheme.NTLM;
}
newRealm = realmBuilder.setScheme(authScheme)
.setUri(URI.create(request.getUrl()).getPath())
.setMethodName(request.getMethod()).build();
}
return newRealm;
}
private Realm ntlmProxyChallenge(final List<String> wwwAuth,
final Request request, final ProxyServer proxyServer,
final FluentCaseInsensitiveStringsMap headers, final Realm realm,
final NettyResponseFuture<?> future) throws NTLMEngineException {
future.getAndSetAuth(false);
headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION);
if (wwwAuth.get(0).startsWith("NTLM ")) {
final String serverChallenge = wwwAuth.get(0).trim()
.substring("NTLM ".length());
final String challengeHeader = ntlmEngine.generateType3Msg(
proxyServer.getPrincipal(), proxyServer.getPassword(),
proxyServer.getNtlmDomain(), proxyServer.getHost(),
serverChallenge);
headers.add(HttpHeaders.Names.PROXY_AUTHORIZATION, "NTLM "
+ challengeHeader);
}
Realm newRealm;
Realm.RealmBuilder realmBuilder;
if (realm != null) {
realmBuilder = new Realm.RealmBuilder().clone(realm);
} else {
realmBuilder = new Realm.RealmBuilder();
}
newRealm = realmBuilder
// .setScheme(realm.getAuthScheme())
.setUri(URI.create(request.getUrl()).getPath())
.setMethodName(request.getMethod()).build();
return newRealm;
}
private void drainChannel(final ChannelHandlerContext ctx,
final NettyResponseFuture<?> future, final boolean keepAlive,
final URI uri) {
ctx.setAttachment(new AsyncCallable(future) {
@Override
public Object call() throws Exception {
if (keepAlive
&& ctx.getChannel().isReadable()
&& connectionsPool.offer(
AsyncHttpProviderUtils.getBaseUrl(uri),
ctx.getChannel())) {
return null;
}
finishChannel(ctx);
return null;
}
@Override
public String toString() {
return String.format("Draining task for channel %s",
ctx.getChannel());
}
});
}
private FilterContext handleIoException(FilterContext fc,
final NettyResponseFuture<?> future) {
for (final IOExceptionFilter asyncFilter : config
.getIOExceptionFilters()) {
try {
fc = asyncFilter.filter(fc);
if (fc == null) {
throw new NullPointerException("FilterContext is null");
}
} catch (final FilterException efe) {
abort(future, efe);
}
}
return fc;
}
private void replayRequest(final NettyResponseFuture<?> future,
final FilterContext fc, final HttpResponse response,
final ChannelHandlerContext ctx) throws IOException {
final Request newRequest = fc.getRequest();
future.setAsyncHandler(fc.getAsyncHandler());
future.setState(NettyResponseFuture.STATE.NEW);
future.touch();
drainChannel(ctx, future, future.getKeepAlive(), future.getURI());
nextRequest(newRequest, future);
return;
}
private List<String> getAuthorizationToken(
final List<Entry<String, String>> list, final String headerAuth) {
final ArrayList<String> l = new ArrayList<String>();
for (final Entry<String, String> e : list) {
if (e.getKey().equalsIgnoreCase(headerAuth)) {
l.add(e.getValue().trim());
}
}
return l;
}
private void nextRequest(final Request request,
final NettyResponseFuture<?> future) throws IOException {
nextRequest(request, future, true);
}
private void nextRequest(final Request request,
final NettyResponseFuture<?> future, final boolean useCache)
throws IOException {
execute(request, future, useCache, true, true);
}
private void abort(final NettyResponseFuture<?> future, final Throwable t) {
final Channel channel = future.channel();
if (channel != null && openChannels.contains(channel)) {
closeChannel(channel.getPipeline().getContext(
NettyAsyncHttpProvider.class));
openChannels.remove(channel);
}
if (!future.isCancelled() && !future.isDone()) {
}
future.abort(t);
}
private void upgradeProtocol(final ChannelPipeline p, final String scheme)
throws IOException, GeneralSecurityException {
if (p.get(HTTP_HANDLER) != null) {
p.remove(HTTP_HANDLER);
}
if (scheme.startsWith(HTTPS)) {
if (p.get(SSL_HANDLER) == null) {
p.addFirst(HTTP_HANDLER, new HttpClientCodec());
p.addFirst(SSL_HANDLER, new SslHandler(createSSLEngine()));
} else {
p.addAfter(SSL_HANDLER, HTTP_HANDLER, new HttpClientCodec());
}
} else {
p.addFirst(HTTP_HANDLER, new HttpClientCodec());
}
}
@Override
public void channelClosed(final ChannelHandlerContext ctx,
final ChannelStateEvent e) throws Exception {
if (isClose.get()) {
return;
}
connectionsPool.removeAll(ctx.getChannel());
try {
super.channelClosed(ctx, e);
} catch (final Exception ex) {
}
if (ctx.getAttachment() instanceof AsyncCallable) {
final AsyncCallable ac = (AsyncCallable) ctx.getAttachment();
ctx.setAttachment(ac.future());
ac.call();
return;
}
if (ctx.getAttachment() instanceof NettyResponseFuture<?>) {
final NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx
.getAttachment();
future.touch();
if (config.getIOExceptionFilters().size() > 0) {
FilterContext<?> fc = new FilterContext.FilterContextBuilder()
.asyncHandler(future.getAsyncHandler())
.request(future.getRequest())
.ioException(new IOException("Channel Closed")).build();
fc = handleIoException(fc, future);
if (fc.replayRequest() && !future.cannotBeReplay()) {
replayRequest(future, fc, null, ctx);
return;
}
}
final Protocol p = httpProtocol;
p.onClose(ctx, e);
if (future != null && !future.isDone() && !future.isCancelled()) {
if (!remotelyClosed(ctx.getChannel(), future)) {
abort(future,
new IOException("Remotely Closed "
+ ctx.getChannel()));
}
} else {
closeChannel(ctx);
}
}
}
protected boolean remotelyClosed(final Channel channel,
NettyResponseFuture<?> future) {
if (isClose.get()) {
return false;
}
connectionsPool.removeAll(channel);
if (future == null
&& channel.getPipeline()
.getContext(NettyAsyncHttpProvider.class)
.getAttachment() != null
&& NettyResponseFuture.class.isAssignableFrom(channel
.getPipeline().getContext(NettyAsyncHttpProvider.class)
.getAttachment().getClass())) {
future = (NettyResponseFuture<?>) channel.getPipeline()
.getContext(NettyAsyncHttpProvider.class).getAttachment();
}
if (future == null || future.cannotBeReplay()) {
return false;
}
future.setState(NettyResponseFuture.STATE.RECONNECTED);
try {
nextRequest(future.getRequest(), future);
return true;
} catch (final IOException iox) {
future.setState(NettyResponseFuture.STATE.CLOSED);
future.abort(iox);
}
return false;
}
private void markAsDone(final NettyResponseFuture<?> future,
final ChannelHandlerContext ctx) throws MalformedURLException {
// We need to make sure everything is OK before adding the connection
// back to the pool.
try {
future.done(null);
} catch (final Throwable t) {
// Never propagate exception once we know we are done.
}
if (!future.getKeepAlive() || !ctx.getChannel().isReadable()) {
closeChannel(ctx);
}
}
private void finishUpdate(final NettyResponseFuture<?> future,
final ChannelHandlerContext ctx, final boolean lastValidChunk)
throws IOException {
if (lastValidChunk && future.getKeepAlive()) {
drainChannel(ctx, future, future.getKeepAlive(), future.getURI());
} else {
if (future.getKeepAlive()
&& ctx.getChannel().isReadable()
&& connectionsPool.offer(
AsyncHttpProviderUtils.getBaseUrl(future.getURI()),
ctx.getChannel())) {
markAsDone(future, ctx);
return;
}
finishChannel(ctx);
}
markAsDone(future, ctx);
}
@SuppressWarnings("unchecked")
private final boolean updateStatusAndInterrupt(final AsyncHandler handler,
final HttpResponseStatus c) throws Exception {
return handler.onStatusReceived(c) != STATE.CONTINUE;
}
@SuppressWarnings("unchecked")
private final boolean updateHeadersAndInterrupt(final AsyncHandler handler,
final HttpResponseHeaders c) throws Exception {
return handler.onHeadersReceived(c) != STATE.CONTINUE;
}
@SuppressWarnings("unchecked")
private final boolean updateBodyAndInterrupt(
final NettyResponseFuture<?> future, final AsyncHandler handler,
final HttpResponseBodyPart c) throws Exception {
final boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE;
if (c.closeUnderlyingConnection()) {
future.setKeepAlive(false);
}
return state;
}
// Simple marker for stopping publishing bytes.
final static class DiscardEvent {
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx,
final ExceptionEvent e) throws Exception {
final Channel channel = e.getChannel();
Throwable cause = e.getCause();
NettyResponseFuture<?> future = null;
try {
if (cause != null
&& ClosedChannelException.class.isAssignableFrom(cause
.getClass())) {
return;
}
if (ctx.getAttachment() instanceof NettyResponseFuture<?>) {
future = (NettyResponseFuture<?>) ctx.getAttachment();
future.attachChannel(null, false);
future.touch();
if (IOException.class.isAssignableFrom(cause.getClass())) {
if (config.getIOExceptionFilters().size() > 0) {
FilterContext<?> fc = new FilterContext.FilterContextBuilder()
.asyncHandler(future.getAsyncHandler())
.request(future.getRequest())
.ioException(new IOException("Channel Closed"))
.build();
fc = handleIoException(fc, future);
if (fc.replayRequest()) {
replayRequest(future, fc, null, ctx);
return;
}
} else {
// Close the channel so the recovering can occurs.
try {
ctx.getChannel().close();
} catch (final Throwable t) {
; // Swallow.
}
return;
}
}
if (abortOnReadCloseException(cause)
|| abortOnWriteCloseException(cause)) {
return;
}
} else if (ctx.getAttachment() instanceof AsyncCallable) {
future = ((AsyncCallable) ctx.getAttachment()).future();
}
} catch (final Throwable t) {
cause = t;
}
if (future != null) {
try {
abort(future, cause);
} catch (final Throwable t) {
}
}
final Protocol p = httpProtocol;
p.onError(ctx, e);
closeChannel(ctx);
ctx.sendUpstream(e);
}
protected static boolean abortOnConnectCloseException(final Throwable cause) {
try {
for (final StackTraceElement element : cause.getStackTrace()) {
if (element.getClassName().equals(
"sun.nio.ch.SocketChannelImpl")
&& element.getMethodName().equals("checkConnect")) {
return true;
}
}
if (cause.getCause() != null) {
return abortOnConnectCloseException(cause.getCause());
}
} catch (final Throwable t) {
}
return false;
}
protected static boolean abortOnDisconnectException(final Throwable cause) {
try {
for (final StackTraceElement element : cause.getStackTrace()) {
if (element.getClassName().equals(
"org.jboss.netty.handler.ssl.SslHandler")
&& element.getMethodName()
.equals("channelDisconnected")) {
return true;
}
}
if (cause.getCause() != null) {
return abortOnConnectCloseException(cause.getCause());
}
} catch (final Throwable t) {
}
return false;
}
protected static boolean abortOnReadCloseException(final Throwable cause) {
for (final StackTraceElement element : cause.getStackTrace()) {
if (element.getClassName().equals("sun.nio.ch.SocketDispatcher")
&& element.getMethodName().equals("read")) {
return true;
}
}
if (cause.getCause() != null) {
return abortOnReadCloseException(cause.getCause());
}
return false;
}
protected static boolean abortOnWriteCloseException(final Throwable cause) {
for (final StackTraceElement element : cause.getStackTrace()) {
if (element.getClassName().equals("sun.nio.ch.SocketDispatcher")
&& element.getMethodName().equals("write")) {
return true;
}
}
if (cause.getCause() != null) {
return abortOnReadCloseException(cause.getCause());
}
return false;
}
private final static int computeAndSetContentLength(final Request request,
final HttpRequest r) {
int length = (int) request.getContentLength();
if (length == -1
&& r.getHeader(HttpHeaders.Names.CONTENT_LENGTH) != null) {
length = Integer.valueOf(r
.getHeader(HttpHeaders.Names.CONTENT_LENGTH));
}
if (length >= 0) {
r.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
String.valueOf(length));
}
return length;
}
public static <T> NettyResponseFuture<T> newFuture(final URI uri,
final Request request, final AsyncHandler<T> asyncHandler,
final HttpRequest nettyRequest, final AsyncHttpClientConfig config,
final NettyAsyncHttpProvider provider) {
final NettyResponseFuture<T> f = new NettyResponseFuture<T>(uri,
request, asyncHandler, nettyRequest, requestTimeout(config,
request.getPerRequestConfig()),
config.getIdleConnectionTimeoutInMs(), provider);
if (request.getHeaders().getFirstValue("Expect") != null
&& request.getHeaders().getFirstValue("Expect")
.equalsIgnoreCase("100-Continue")) {
f.getAndSetWriteBody(false);
}
return f;
}
private class ProgressListener implements ChannelFutureProgressListener {
private final boolean notifyHeaders;
private final AsyncHandler asyncHandler;
private final NettyResponseFuture<?> future;
public ProgressListener(final boolean notifyHeaders,
final AsyncHandler asyncHandler,
final NettyResponseFuture<?> future) {
this.notifyHeaders = notifyHeaders;
this.asyncHandler = asyncHandler;
this.future = future;
}
@Override
public void operationComplete(final ChannelFuture cf) {
// The write operation failed. If the channel was cached, it means
// it got asynchronously closed.
// Let's retry a second time.
final Throwable cause = cf.getCause();
if (cause != null
&& future.getState() != NettyResponseFuture.STATE.NEW) {
if (IllegalStateException.class.isAssignableFrom(cause
.getClass())) {
try {
cf.getChannel().close();
} catch (final RuntimeException ex) {
}
return;
}
if (ClosedChannelException.class.isAssignableFrom(cause
.getClass())
|| abortOnReadCloseException(cause)
|| abortOnWriteCloseException(cause)) {
try {
cf.getChannel().close();
} catch (final RuntimeException ex) {
}
return;
} else {
future.abort(cause);
}
return;
}
future.touch();
/**
* We need to make sure we aren't in the middle of an authorization
* process before publishing events as we will re-publish again the
* same event after the authorization, causing unpredictable
* behavior.
*/
final Realm realm = future.getRequest().getRealm() != null ? future
.getRequest().getRealm() : NettyAsyncHttpProvider.this
.getConfig().getRealm();
final boolean startPublishing = future.isInAuth() || realm == null
|| realm.getUsePreemptiveAuth() == true;
if (startPublishing
&& ProgressAsyncHandler.class.isAssignableFrom(asyncHandler
.getClass())) {
if (notifyHeaders) {
ProgressAsyncHandler.class.cast(asyncHandler)
.onHeaderWriteCompleted();
} else {
ProgressAsyncHandler.class.cast(asyncHandler)
.onContentWriteCompleted();
}
}
}
@Override
public void operationProgressed(final ChannelFuture cf,
final long amount, final long current, final long total) {
future.touch();
if (ProgressAsyncHandler.class.isAssignableFrom(asyncHandler
.getClass())) {
ProgressAsyncHandler.class.cast(asyncHandler)
.onContentWriteProgress(amount, current, total);
}
}
}
/**
* Because some implementation of the ThreadSchedulingService do not clean
* up cancel task until they try to run them, we wrap the task with the
* future so the when the NettyResponseFuture cancel the reaper future this
* wrapper will release the references to the channel and the
* nettyResponseFuture immediately. Otherwise, the memory referenced this
* way will only be released after the request timeout period which can be
* arbitrary long.
*/
private final class ReaperFuture implements Future, Runnable {
private Future scheduledFuture;
private Channel channel;
private NettyResponseFuture<?> nettyResponseFuture;
public ReaperFuture(final Channel channel,
final NettyResponseFuture<?> nettyResponseFuture) {
this.channel = channel;
this.nettyResponseFuture = nettyResponseFuture;
}
public void setScheduledFuture(final Future scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
/**
* @Override
*/
@Override
public synchronized boolean cancel(final boolean mayInterruptIfRunning) {
// cleanup references to allow gc to reclaim memory independently
// of this Future lifecycle
this.channel = null;
this.nettyResponseFuture = null;
return this.scheduledFuture.cancel(mayInterruptIfRunning);
}
/**
* @Override
*/
@Override
public Object get() throws InterruptedException, ExecutionException {
return this.scheduledFuture.get();
}
/**
* @Override
*/
@Override
public Object get(final long timeout, final TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
return this.scheduledFuture.get(timeout, unit);
}
/**
* @Override
*/
@Override
public boolean isCancelled() {
return this.scheduledFuture.isCancelled();
}
/**
* @Override
*/
@Override
public boolean isDone() {
return this.scheduledFuture.isDone();
}
/**
* @Override
*/
@Override
public synchronized void run() {
if (isClose.get()) {
cancel(true);
return;
}
if (this.nettyResponseFuture != null
&& this.nettyResponseFuture.hasExpired()
&& !this.nettyResponseFuture.isDone()
&& !this.nettyResponseFuture.isCancelled()) {
int requestTimeout = config.getRequestTimeoutInMs();
final PerRequestConfig p = this.nettyResponseFuture
.getRequest().getPerRequestConfig();
if (p != null && p.getRequestTimeoutInMs() != -1) {
requestTimeout = p.getRequestTimeoutInMs();
}
abort(this.nettyResponseFuture,
new TimeoutException(String
.format("No response received after %s",
requestTimeout)));
this.nettyResponseFuture = null;
this.channel = null;
}
if (this.nettyResponseFuture == null
|| this.nettyResponseFuture.isDone()
|| this.nettyResponseFuture.isCancelled()) {
cancel(true);
}
}
}
private abstract class AsyncCallable implements Callable<Object> {
private final NettyResponseFuture<?> future;
public AsyncCallable(final NettyResponseFuture<?> future) {
this.future = future;
}
@Override
abstract public Object call() throws Exception;
public NettyResponseFuture<?> future() {
return future;
}
}
public static class ThreadLocalBoolean extends ThreadLocal<Boolean> {
private final boolean defaultValue;
public ThreadLocalBoolean() {
this(false);
}
public ThreadLocalBoolean(final boolean defaultValue) {
this.defaultValue = defaultValue;
}
@Override
protected Boolean initialValue() {
return defaultValue ? Boolean.TRUE : Boolean.FALSE;
}
}
public static class OptimizedFileRegion implements FileRegion {
private final FileChannel file;
private final RandomAccessFile raf;
private final long position;
private final long count;
private long byteWritten;
public OptimizedFileRegion(final RandomAccessFile raf,
final long position, final long count) {
this.raf = raf;
this.file = raf.getChannel();
this.position = position;
this.count = count;
}
@Override
public long getPosition() {
return position;
}
@Override
public long getCount() {
return count;
}
@Override
public long transferTo(final WritableByteChannel target,
final long position) throws IOException {
final long count = this.count - position;
if (count < 0 || position < 0) {
throw new IllegalArgumentException("position out of range: "
+ position + " (expected: 0 - " + (this.count - 1)
+ ")");
}
if (count == 0) {
return 0L;
}
final long bw = file.transferTo(this.position + position, count,
target);
byteWritten += bw;
if (byteWritten == raf.length()) {
releaseExternalResources();
}
return bw;
}
@Override
public void releaseExternalResources() {
try {
file.close();
} catch (final IOException e) {
}
try {
raf.close();
} catch (final IOException e) {
}
}
}
private static class NettyTransferAdapter extends
TransferCompletionHandler.TransferAdapter {
private final ChannelBuffer content;
private final FileInputStream file;
private int byteRead = 0;
public NettyTransferAdapter(
final FluentCaseInsensitiveStringsMap headers,
final ChannelBuffer content, final File file)
throws IOException {
super(headers);
this.content = content;
if (file != null) {
this.file = new FileInputStream(file);
} else {
this.file = null;
}
}
@Override
public void getBytes(final byte[] bytes) {
if (content.writableBytes() != 0) {
content.getBytes(byteRead, bytes);
byteRead += bytes.length;
} else if (file != null) {
try {
byteRead += file.read(bytes);
} catch (final IOException e) {
}
}
}
}
protected AsyncHttpClientConfig getConfig() {
return config;
}
private static class NonConnectionsPool implements
ConnectionsPool<String, Channel> {
@Override
public boolean offer(final String uri, final Channel connection) {
return false;
}
@Override
public Channel poll(final String uri) {
return null;
}
@Override
public boolean removeAll(final Channel connection) {
return false;
}
@Override
public boolean canCacheConnection() {
return true;
}
@Override
public void destroy() {
}
}
private static final boolean validateWebSocketRequest(
final Request request, final AsyncHandler<?> asyncHandler) {
if (request.getMethod() != "GET"
|| !WebSocketUpgradeHandler.class.isAssignableFrom(asyncHandler
.getClass())) {
return false;
}
return true;
}
private final class HttpProtocol implements Protocol {
@Override
public void handle(final ChannelHandlerContext ctx, final MessageEvent e)
throws Exception {
final NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx
.getAttachment();
future.touch();
// The connect timeout occured.
if (future.isCancelled() || future.isDone()) {
finishChannel(ctx);
return;
}
final HttpRequest nettyRequest = future.getNettyRequest();
AsyncHandler handler = future.getAsyncHandler();
final Request request = future.getRequest();
HttpResponse response = null;
try {
if (e.getMessage() instanceof HttpResponse) {
response = (HttpResponse) e.getMessage();
// Required if there is some trailing headers.
future.setHttpResponse(response);
final int statusCode = response.getStatus().getCode();
final String ka = response
.getHeader(HttpHeaders.Names.CONNECTION);
future.setKeepAlive(ka == null
|| ka.toLowerCase().equals("keep-alive"));
final List<String> wwwAuth = getAuthorizationToken(
response.getHeaders(),
HttpHeaders.Names.WWW_AUTHENTICATE);
final Realm realm = request.getRealm() != null ? request
.getRealm() : config.getRealm();
final HttpResponseStatus status = new ResponseStatus(
future.getURI(), response,
NettyAsyncHttpProvider.this);
final HttpResponseHeaders responseHeaders = new ResponseHeaders(
future.getURI(), response,
NettyAsyncHttpProvider.this);
FilterContext fc = new FilterContext.FilterContextBuilder()
.asyncHandler(handler).request(request)
.responseStatus(status)
.responseHeaders(responseHeaders).build();
for (final ResponseFilter asyncFilter : config
.getResponseFilters()) {
try {
fc = asyncFilter.filter(fc);
if (fc == null) {
throw new NullPointerException(
"FilterContext is null");
}
} catch (final FilterException efe) {
abort(future, efe);
}
}
// The handler may have been wrapped.
handler = fc.getAsyncHandler();
future.setAsyncHandler(handler);
// The request has changed
if (fc.replayRequest()) {
replayRequest(future, fc, response, ctx);
return;
}
Realm newRealm = null;
final ProxyServer proxyServer = request.getProxyServer() != null ? request
.getProxyServer() : config.getProxyServer();
final FluentCaseInsensitiveStringsMap headers = request
.getHeaders();
final RequestBuilder builder = new RequestBuilder(
future.getRequest());
// if (realm != null &&
// !future.getURI().getPath().equalsIgnoreCase(realm.getUri()))
// {
// builder.setUrl(future.getURI().toString());
// }
if (statusCode == 401 && wwwAuth.size() > 0
&& !future.getAndSetAuth(true)) {
future.setState(NettyResponseFuture.STATE.NEW);
// NTLM
if (!wwwAuth.contains("Kerberos")
&& (wwwAuth.contains("NTLM") || (wwwAuth
.contains("Negotiate")))) {
newRealm = ntlmChallenge(wwwAuth, request,
proxyServer, headers, realm, future);
// SPNEGO KERBEROS
} else if (wwwAuth.contains("Negotiate")) {
newRealm = kerberosChallenge(wwwAuth, request,
proxyServer, headers, realm, future);
if (newRealm == null)
return;
} else {
Realm.RealmBuilder realmBuilder;
if (realm != null) {
realmBuilder = new Realm.RealmBuilder().clone(
realm).setScheme(realm.getAuthScheme());
} else {
realmBuilder = new Realm.RealmBuilder();
}
newRealm = realmBuilder
.setUri(URI.create(request.getUrl())
.getPath())
.setMethodName(request.getMethod())
.setUsePreemptiveAuth(true)
.parseWWWAuthenticateHeader(wwwAuth.get(0))
.build();
}
final Realm nr = new Realm.RealmBuilder()
.clone(newRealm).setUri(request.getUrl())
.build();
final AsyncCallable ac = new AsyncCallable(future) {
@Override
public Object call() throws Exception {
drainChannel(ctx, future,
future.getKeepAlive(), future.getURI());
nextRequest(builder.setHeaders(headers)
.setRealm(nr).build(), future);
return null;
}
};
if (future.getKeepAlive() && response.isChunked()) {
// We must make sure there is no bytes left before
// executing the next request.
ctx.setAttachment(ac);
} else {
ac.call();
}
return;
}
if (statusCode == 100) {
future.getAndSetWriteHeaders(false);
future.getAndSetWriteBody(true);
writeRequest(ctx.getChannel(), config, future,
nettyRequest);
return;
}
final List<String> proxyAuth = getAuthorizationToken(
response.getHeaders(),
HttpHeaders.Names.PROXY_AUTHENTICATE);
if (statusCode == 407 && proxyAuth.size() > 0
&& !future.getAndSetAuth(true)) {
future.setState(NettyResponseFuture.STATE.NEW);
if (!proxyAuth.contains("Kerberos")
&& (proxyAuth.get(0).contains("NTLM") || (proxyAuth
.contains("Negotiate")))) {
newRealm = ntlmProxyChallenge(proxyAuth, request,
proxyServer, headers, realm, future);
// SPNEGO KERBEROS
} else if (proxyAuth.contains("Negotiate")) {
newRealm = kerberosChallenge(proxyAuth, request,
proxyServer, headers, realm, future);
if (newRealm == null)
return;
} else {
newRealm = future.getRequest().getRealm();
}
final Request req = builder.setHeaders(headers)
.setRealm(newRealm).build();
future.setReuseChannel(true);
future.setConnectAllowed(true);
nextRequest(req, future);
return;
}
if (future.getNettyRequest().getMethod()
.equals(HttpMethod.CONNECT)
&& statusCode == 200) {
if (future.getKeepAlive()) {
future.attachChannel(ctx.getChannel(), true);
}
try {
upgradeProtocol(ctx.getChannel().getPipeline(),
request.getUrl());
} catch (final Throwable ex) {
abort(future, ex);
}
final Request req = builder.build();
future.setReuseChannel(true);
future.setConnectAllowed(false);
nextRequest(req, future);
return;
}
final boolean redirectEnabled = request
.isRedirectOverrideSet() ? request
.isRedirectEnabled() : config.isRedirectEnabled();
if (redirectEnabled
&& (statusCode == 302 || statusCode == 301
|| statusCode == 303 || statusCode == 307)) {
if (future.incrementAndGetCurrentRedirectCount() < config
.getMaxRedirects()) {
// We must allow 401 handling again.
future.getAndSetAuth(false);
final String location = response
.getHeader(HttpHeaders.Names.LOCATION);
final URI uri = AsyncHttpProviderUtils
.getRedirectUri(future.getURI(), location);
final boolean stripQueryString = config
.isRemoveQueryParamOnRedirect();
if (!uri.toString().equalsIgnoreCase(
future.getURI().toString())) {
final RequestBuilder nBuilder = stripQueryString ? new RequestBuilder(
future.getRequest())
.setQueryParameters(null)
: new RequestBuilder(
future.getRequest());
if (!(statusCode < 302 || statusCode > 303)
&& !(statusCode == 302 && config
.isStrict302Handling())) {
nBuilder.setMethod("GET");
}
final URI initialConnectionUri = future
.getURI();
final boolean initialConnectionKeepAlive = future
.getKeepAlive();
future.setURI(uri);
final String newUrl = uri.toString();
for (final String cookieStr : future
.getHttpResponse().getHeaders(
HttpHeaders.Names.SET_COOKIE)) {
final Cookie c = AsyncHttpProviderUtils
.parseCookie(cookieStr);
nBuilder.addOrReplaceCookie(c);
}
for (final String cookieStr : future
.getHttpResponse().getHeaders(
HttpHeaders.Names.SET_COOKIE2)) {
final Cookie c = AsyncHttpProviderUtils
.parseCookie(cookieStr);
nBuilder.addOrReplaceCookie(c);
}
final AsyncCallable ac = new AsyncCallable(
future) {
@Override
public Object call() throws Exception {
if (initialConnectionKeepAlive
&& ctx.getChannel()
.isReadable()
&& connectionsPool
.offer(AsyncHttpProviderUtils
.getBaseUrl(initialConnectionUri),
ctx.getChannel())) {
return null;
}
finishChannel(ctx);
return null;
}
};
if (response.isChunked()) {
// We must make sure there is no bytes left
// before executing the next request.
ctx.setAttachment(ac);
} else {
ac.call();
}
nextRequest(nBuilder.setUrl(newUrl).build(),
future);
return;
}
} else {
throw new MaxRedirectException(
"Maximum redirect reached: "
+ config.getMaxRedirects());
}
}
if (!future.getAndSetStatusReceived(true)
&& updateStatusAndInterrupt(handler, status)) {
finishUpdate(future, ctx, response.isChunked());
return;
} else if (updateHeadersAndInterrupt(handler,
responseHeaders)) {
finishUpdate(future, ctx, response.isChunked());
return;
} else if (!response.isChunked()) {
if (response.getContent().readableBytes() != 0) {
updateBodyAndInterrupt(future, handler,
new ResponseBodyPart(future.getURI(),
response,
NettyAsyncHttpProvider.this, true));
}
finishUpdate(future, ctx, false);
return;
}
if (nettyRequest.getMethod().equals(HttpMethod.HEAD)) {
updateBodyAndInterrupt(future, handler,
new ResponseBodyPart(future.getURI(), response,
NettyAsyncHttpProvider.this, true));
markAsDone(future, ctx);
drainChannel(ctx, future, future.getKeepAlive(),
future.getURI());
}
} else if (e.getMessage() instanceof HttpChunk) {
final HttpChunk chunk = (HttpChunk) e.getMessage();
if (handler != null) {
if (chunk.isLast()
|| updateBodyAndInterrupt(future, handler,
new ResponseBodyPart(future.getURI(),
null,
NettyAsyncHttpProvider.this,
chunk, chunk.isLast()))) {
if (chunk instanceof DefaultHttpChunkTrailer) {
updateHeadersAndInterrupt(handler,
new ResponseHeaders(future.getURI(),
future.getHttpResponse(),
NettyAsyncHttpProvider.this,
(HttpChunkTrailer) chunk));
}
finishUpdate(future, ctx, !chunk.isLast());
}
}
}
} catch (final Exception t) {
if (IOException.class.isAssignableFrom(t.getClass())
&& config.getIOExceptionFilters().size() > 0) {
FilterContext<?> fc = new FilterContext.FilterContextBuilder()
.asyncHandler(future.getAsyncHandler())
.request(future.getRequest())
.ioException(IOException.class.cast(t)).build();
fc = handleIoException(fc, future);
if (fc.replayRequest()) {
replayRequest(future, fc, response, ctx);
return;
}
}
try {
abort(future, t);
} finally {
finishUpdate(future, ctx, false);
throw t;
}
}
}
@Override
public void onError(final ChannelHandlerContext ctx,
final ExceptionEvent e) {
}
@Override
public void onClose(final ChannelHandlerContext ctx,
final ChannelStateEvent e) {
}
}
}