/*
* 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 java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.HostnameVerifier;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.ssl.SslHandler;
import com.ning.http.client.AsyncHandler;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Request;
import com.ning.http.util.AllowAllHostnameVerifier;
/**
* Non Blocking connect.
*/
final class NettyConnectListener<T> implements ChannelFutureListener {
private final AsyncHttpClientConfig config;
private final NettyResponseFuture<T> future;
private final HttpRequest nettyRequest;
private final AtomicBoolean handshakeDone = new AtomicBoolean(false);
private NettyConnectListener(final AsyncHttpClientConfig config,
final NettyResponseFuture<T> future, final HttpRequest nettyRequest) {
this.config = config;
this.future = future;
this.nettyRequest = nettyRequest;
}
public NettyResponseFuture<T> future() {
return future;
}
@Override
public final void operationComplete(final ChannelFuture f) throws Exception {
if (f.isSuccess()) {
final Channel channel = f.getChannel();
channel.getPipeline().getContext(NettyAsyncHttpProvider.class)
.setAttachment(future);
final SslHandler sslHandler = (SslHandler) channel.getPipeline()
.get(NettyAsyncHttpProvider.SSL_HANDLER);
if (!handshakeDone.getAndSet(true) && (sslHandler != null)) {
((SslHandler) channel.getPipeline().get(
NettyAsyncHttpProvider.SSL_HANDLER)).handshake()
.addListener(this);
return;
}
final HostnameVerifier v = config.getHostnameVerifier();
if (sslHandler != null
&& !AllowAllHostnameVerifier.class.isAssignableFrom(v
.getClass())) {
// TODO: channel.getRemoteAddress()).getHostName() is very
// expensive. Should cache the result.
if (!v.verify(
InetSocketAddress.class
.cast(channel.getRemoteAddress()).getHostName(),
sslHandler.getEngine().getSession())) {
throw new ConnectException("HostnameVerifier exception.");
}
}
future.provider().writeRequest(f.getChannel(), config, future,
nettyRequest);
} else {
final Throwable cause = f.getCause();
if (future.canRetry()
&& cause != null
&& (NettyAsyncHttpProvider
.abortOnDisconnectException(cause)
|| ClosedChannelException.class
.isAssignableFrom(cause.getClass()) || future
.getState() != NettyResponseFuture.STATE.NEW)) {
if (future.provider().remotelyClosed(f.getChannel(), future)) {
return;
}
}
final boolean printCause = f.getCause() != null
&& cause.getMessage() != null;
final ConnectException e = new ConnectException(
printCause ? cause.getMessage() + " to "
+ future.getURI().toString() : future.getURI()
.toString());
if (cause != null) {
e.initCause(cause);
}
future.abort(e);
}
}
public static class Builder<T> {
private final AsyncHttpClientConfig config;
private final Request request;
private final AsyncHandler<T> asyncHandler;
private NettyResponseFuture<T> future;
private final NettyAsyncHttpProvider provider;
private final ChannelBuffer buffer;
public Builder(final AsyncHttpClientConfig config,
final Request request, final AsyncHandler<T> asyncHandler,
final NettyAsyncHttpProvider provider,
final ChannelBuffer buffer) {
this.config = config;
this.request = request;
this.asyncHandler = asyncHandler;
this.future = null;
this.provider = provider;
this.buffer = buffer;
}
public Builder(final AsyncHttpClientConfig config,
final Request request, final AsyncHandler<T> asyncHandler,
final NettyResponseFuture<T> future,
final NettyAsyncHttpProvider provider,
final ChannelBuffer buffer) {
this.config = config;
this.request = request;
this.asyncHandler = asyncHandler;
this.future = future;
this.provider = provider;
this.buffer = buffer;
}
public NettyConnectListener<T> build(final URI uri) throws IOException {
final HttpRequest nettyRequest = NettyAsyncHttpProvider
.buildRequest(config, request, uri, true, buffer);
if (future == null) {
future = NettyAsyncHttpProvider.newFuture(uri, request,
asyncHandler, nettyRequest, config, provider);
} else {
future.setNettyRequest(nettyRequest);
future.setRequest(request);
}
return new NettyConnectListener<T>(config, future, nettyRequest);
}
}
}