package com.intrbiz.bergamot.check.tcp;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.util.concurrent.GenericFutureListener;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import com.intrbiz.util.IBThreadFactory;
/**
* A non-blocking, asynchronous HTTP checker
*/
public class TCPChecker
{
private EventLoopGroup eventLoop;
private int defaultRequestTimeoutSeconds;
private int defaultConnectTimeoutSeconds;
public TCPChecker(int threads, int defaultConnectTimeoutSeconds, int defaultRequestTimeoutSeconds)
{
this.defaultRequestTimeoutSeconds = defaultRequestTimeoutSeconds;
this.defaultConnectTimeoutSeconds = defaultConnectTimeoutSeconds;
// setup the Netty event loop
this.eventLoop = new NioEventLoopGroup(threads, new IBThreadFactory("bergamot-http-checker", false));
}
public TCPChecker()
{
this((Runtime.getRuntime().availableProcessors() * 2) + 4, 5, 60);
}
public TCPChecker(int threads)
{
this(threads, 5, 60);
}
public int getDefaultRequestTimeoutSeconds()
{
return defaultRequestTimeoutSeconds;
}
public int getDefaultConnectTimeoutSeconds()
{
return defaultConnectTimeoutSeconds;
}
protected void submit(
final String host,
final int port,
final int connectTimeout,
final int requestTimeout,
final Consumer<TCPCheckResponse> responseHandler,
final Consumer<Throwable> errorHandler
)
{
// configure the client
Bootstrap b = new Bootstrap();
b.group(this.eventLoop);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) TimeUnit.SECONDS.toMillis(connectTimeout < 0 ? this.defaultConnectTimeoutSeconds : connectTimeout));
b.handler(new ChannelInitializer<SocketChannel>()
{
@Override
public void initChannel(SocketChannel ch) throws Exception
{
// Timeouts
ch.pipeline().addLast(
new ReadTimeoutHandler( requestTimeout < 0 ? TCPChecker.this.defaultRequestTimeoutSeconds : requestTimeout /* seconds */ ),
new WriteTimeoutHandler( requestTimeout < 0 ? TCPChecker.this.defaultRequestTimeoutSeconds : requestTimeout /* seconds */ )
);
// HTTP handling
ch.pipeline().addLast(
new HttpClientCodec(),
new HttpContentDecompressor(),
new HttpObjectAggregator(1 * 1024 * 1024 /* 1 MiB */),
new TCPClientHandler(responseHandler, errorHandler)
);
}
});
// connect the client
b.connect(host, port).addListener(new GenericFutureListener<ChannelFuture>()
{
@Override
public void operationComplete(ChannelFuture future) throws Exception
{
if (future.isDone() && (!future.isSuccess()))
{
errorHandler.accept(future.cause());
}
}
});
}
public TCPCheckBuilder check()
{
return new TCPCheckBuilder() {
@Override
protected void submit(
String address,
int port,
int connectTimeout,
int requestTimeout,
Consumer<TCPCheckResponse> responseHandler,
Consumer<Throwable> errorHandler
)
{
TCPChecker.this.submit(
address,
port,
connectTimeout,
requestTimeout,
responseHandler,
errorHandler
);
}
};
}
public void shutdown()
{
try
{
this.eventLoop.shutdownGracefully().await();
}
catch (InterruptedException e)
{
}
}
}