package org.oneandone.idev.johanna;
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import java.nio.charset.Charset;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.oneandone.idev.johanna.netty.JohannaServerHandler;
import org.oneandone.idev.johanna.store.SessionStore;
import org.oneandone.idev.johanna.store.id.IdentifierFactory;
import org.oneandone.idev.johanna.store.id.IdentifierFlavor;
/**
* Discards any incoming data.
*/
public class JohannahServer {
private static final Logger LOG = Logger.getLogger(JohannahServer.class.getName());
static final int MAX_THREADS = 1;
@Option(name = "--help", help = true, usage = "This command line help")
private boolean help;
@Option(name = "--debug", usage = "Whether to log with DEBUG level")
private boolean debug;
@Option(name = "--port", aliases = "-p", usage = "The TCP/IP port to bind to")
private int port = 2001;
@Option(name = "--host", aliases = "-h", usage = "The IP of the REDIS server to bind to (see --backend, defaults to 127.0.0.1)", metaVar = "IP")
private String host= "127.0.0.1";
@Option(name = "--backend", aliases = "-b", usage = "The session storage backend to use")
private SessionStoreFactory backendName = SessionStoreFactory.MEMORY;
@Option(name = "--identifier", aliases = "-i", usage = "The identifier factory to use")
private IdentifierFlavor identifierFlavor = IdentifierFlavor.MD5;
@Option(name = "--separator", aliases = "-s", usage = "The identifier separator character to use. Separates the IP address prefix and the unique session ID.")
private char identifierSeparator = 'x';
/** Calculated out of {@link #identifierFlavor}. */
private IdentifierFactory identifierFactory;
SessionStore store;
private static void setDebug(boolean debug) {
if (debug) {
LOG.info("Enabling debug mode logging.");
Logger log= Logger.getLogger("");
for (Handler h : log.getHandlers()) {
h.setLevel(Level.ALL);
}
Logger.getLogger("org.oneandone.idev.johanna").setLevel(Level.ALL);
}
}
public void run() {
try {
this.runInternal();
} catch (Exception ex) {
Logger.getLogger(JohannahServer.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void runInternal() throws Exception {
LOG.info("Server startup.");
identifierFactory = new IdentifierFactory(';', identifierFlavor);
this.store = backendName.create(identifierFactory, host);
final EventLoopGroup bossGroup = new NioEventLoopGroup();
final EventLoopGroup workerGroup = new NioEventLoopGroup();
final EventExecutorGroup executorGroup = new DefaultEventExecutorGroup(MAX_THREADS);
try {
LOG.info("===> Starting Johanna Server.");
store.scheduleMaintenanceTask();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(
1024 * 1024 * 1024, Delimiters.lineDelimiter()
));
ch.pipeline().addLast("decoder", new StringDecoder(Charset.forName("iso-8859-1")));
ch.pipeline().addLast("encoder", new StringEncoder(Charset.forName("iso-8859-1")));
ch.pipeline().addLast(executorGroup, new JohannaServerHandler(store));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
LOG.info("===> Shutting down Johanna instance.");
} finally {
LOG.info("---> Cleanup.");
store.cancelMaintenanceTask();
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
executorGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
CmdLineParser cmdLineParser = null;
try {
JohannahServer johannahServer = new JohannahServer();
cmdLineParser = new CmdLineParser(johannahServer);
cmdLineParser.parseArgument(args);
if (johannahServer.help) {
cmdLineParser.printUsage(System.err);
System.err.flush();
return;
}
JohannahServer.setDebug(johannahServer.debug);
johannahServer.run();
} catch (CmdLineException ex) {
System.err.println(ex);
if (cmdLineParser != null) {
cmdLineParser.printUsage(System.err);
System.err.flush();
}
}
}
}