package org.fastcatsearch.http;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.Executors;
import org.fastcatsearch.env.Environment;
import org.fastcatsearch.module.AbstractModule;
import org.fastcatsearch.module.ModuleException;
import org.fastcatsearch.settings.Settings;
import org.fastcatsearch.transport.NetworkExceptionHelper;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpContentCompressor;
import org.jboss.netty.handler.codec.http.HttpContentDecompressor;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.timeout.ReadTimeoutException;
public class HttpTransportModule extends AbstractModule {
private volatile Channel serverChannel;
private volatile ServerBootstrap serverBootstrap;
final int maxContentLength;
final int maxInitialLineLength;
final int maxHeaderSize;
final int maxChunkSize;
private final int workerCount;
// private final boolean blockingServer;
final boolean compression;
private final int compressionLevel; // expected: 0-9, 0 : no compression
final boolean resetCookies;
private final int port;
private final Boolean tcpNoDelay;
private final Boolean tcpKeepAlive;
private final Boolean reuseAddress;
private final long tcpSendBufferSize;
private final long tcpReceiveBufferSize;
final int maxCumulationBufferCapacity; // Integer.MAX_VALUE가 최대이다. 넘는다면 재설정해준다.
final int maxCompositeBufferComponents;
private volatile HttpServerAdapter httpServerAdapter;
public HttpTransportModule(Environment environment, Settings settings, int port) {
super(environment, settings);
this.port = port;
maxContentLength = settings.getInt("max_content_length", settings.getInt("http.max_content_length", 100 * 1024 * 1024));
this.maxChunkSize = settings.getInt("max_chunk_size", settings.getInt("http.max_chunk_size", 8 * 1024));
this.maxHeaderSize = settings.getInt("max_header_size", settings.getInt("http.max_header_size", 8 * 1024));
this.maxInitialLineLength = settings.getInt("max_initial_line_length", settings.getInt("http.max_initial_line_length", 4 * 1024));
// don't reset cookies by default, since I don't think we really need to
// note, parsing cookies was fixed in netty 3.5.1 regarding stack allocation, but still, currently, we don't need cookies
this.resetCookies = settings.getBoolean("reset_cookies", settings.getBoolean("http.reset_cookies", false));
this.maxCumulationBufferCapacity = settings.getInt("max_cumulation_buffer_capacity", 0);
this.maxCompositeBufferComponents = settings.getInt("max_composite_buffer_components", -1);
this.workerCount = settings.getInt("worker_count", Runtime.getRuntime().availableProcessors() * 2);
this.tcpNoDelay = settings.getBoolean("tcp_no_delay", true);
this.tcpKeepAlive = settings.getBoolean("tcp_keep_alive", true);
this.reuseAddress = settings.getBoolean("reuse_address", true);
this.tcpSendBufferSize = settings.getInt("tcp_send_buffer_size");
this.tcpReceiveBufferSize = settings.getInt("tcp_receive_buffer_size");
this.compression = settings.getBoolean("http.compression", false);
this.compressionLevel = settings.getInt("http.compression_level", 6);
}
@Override
protected boolean doLoad() throws ModuleException {
serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool(),
workerCount));
serverBootstrap.setPipelineFactory(new MyChannelPipelineFactory(this));
serverBootstrap.setOption("child.tcpNoDelay", tcpNoDelay);
serverBootstrap.setOption("child.keepAlive", tcpKeepAlive);
if (tcpSendBufferSize > 0) {
serverBootstrap.setOption("child.sendBufferSize", tcpSendBufferSize);
}
if (tcpReceiveBufferSize > 0) {
serverBootstrap.setOption("child.receiveBufferSize", tcpReceiveBufferSize);
}
serverBootstrap.setOption("reuseAddress", reuseAddress);
serverBootstrap.setOption("child.reuseAddress", reuseAddress);
serverChannel = serverBootstrap.bind(new InetSocketAddress(port));
logger.debug("Bound to port [{}]", port);
return true;
}
@Override
protected boolean doUnload() throws ModuleException {
if (serverBootstrap != null) {
serverBootstrap.shutdown();
}
if (serverChannel != null) {
serverChannel.close();
}
return true;
}
static class MyChannelPipelineFactory implements ChannelPipelineFactory {
private final HttpRequestHandler requestHandler;
private final HttpTransportModule transport;
MyChannelPipelineFactory(HttpTransportModule transport) {
this.transport = transport;
this.requestHandler = new HttpRequestHandler(transport);
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// pipeline.addLast("openChannels", transport.serverOpenChannels);
HttpRequestDecoder requestDecoder = new HttpRequestDecoder(transport.maxInitialLineLength, transport.maxHeaderSize,
transport.maxChunkSize);
if (transport.maxCumulationBufferCapacity > 0) {
// if (transport.maxCumulationBufferCapacity > Integer.MAX_VALUE) {
// requestDecoder.setMaxCumulationBufferCapacity(Integer.MAX_VALUE);
// } else {
requestDecoder.setMaxCumulationBufferCapacity((int) transport.maxCumulationBufferCapacity);
// }
}
if (transport.maxCompositeBufferComponents != -1) {
requestDecoder.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
}
pipeline.addLast("decoder", requestDecoder);
if (transport.compression) {
pipeline.addLast("decoder_compress", new HttpContentDecompressor());
}
HttpChunkAggregator httpChunkAggregator = new HttpChunkAggregator(transport.maxContentLength);
if (transport.maxCompositeBufferComponents != -1) {
httpChunkAggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
}
pipeline.addLast("aggregator", httpChunkAggregator);
pipeline.addLast("encoder", new HttpResponseEncoder());
if (transport.compression) {
pipeline.addLast("encoder_compress", new HttpContentCompressor(transport.compressionLevel));
}
pipeline.addLast("handler", requestHandler);
return pipeline;
}
}
public void httpServerAdapter(HttpServerAdapter httpServerAdapter) {
this.httpServerAdapter = httpServerAdapter;
}
public void dispatchRequest(HttpRequest request, HttpChannel httpChannel) {
httpServerAdapter.dispatchRequest(request, httpChannel);
}
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
// logger.error("exceptionCaught", e.getCause());
if (e.getCause() instanceof ReadTimeoutException) {
if (logger.isTraceEnabled()) {
logger.trace("Connection timeout [{}]", ctx.getChannel().getRemoteAddress());
}
ctx.getChannel().close();
} else {
if (!isLoaded) {
// ignore
return;
}
if (!NetworkExceptionHelper.isCloseConnectionException(e.getCause())) {
// logger.warn("Caught exception while handling client http traffic, closing connection {}", e.getCause(), ctx.getChannel());
ctx.getChannel().close();
} else {
// logger.debug("Caught exception while handling client http traffic, closing connection {}", e.getCause(), ctx.getChannel());
ctx.getChannel().close();
}
}
}
}