package com.biasedbit.http.server;
import com.biasedbit.http.client.ssl.BogusSslContextFactory;
import lombok.*;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.http.*;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.util.CharsetUtil;
import javax.net.ssl.SSLEngine;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
/**
* @author <a href="http://biasedbit.com/">Bruno de Carvalho</a>
*/
@RequiredArgsConstructor
public class UploadMirrorHttpServer {
// properties -----------------------------------------------------------------------------------------------------
@Getter private final String host;
@Getter private final int port;
@Getter @Setter private boolean verbose = false;
@Getter @Setter private boolean useSsl = false;
@Getter @Setter private long pauseBefore100Continue = 0;
// internal vars --------------------------------------------------------------------------------------------------
private ServerBootstrap bootstrap;
private DefaultChannelGroup channelGroup;
private boolean running;
// constructors ---------------------------------------------------------------------------------------------------
public UploadMirrorHttpServer(int port) { this(null, port); }
// interface ------------------------------------------------------------------------------------------------------
public boolean init() {
Executor bossExecutor = Executors.newCachedThreadPool();
Executor workerExecutor = Executors.newCachedThreadPool();
ChannelFactory factory = new NioServerSocketChannelFactory(bossExecutor, workerExecutor);
bootstrap = new ServerBootstrap(factory);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override public ChannelPipeline getPipeline()
throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
if (useSsl) {
SSLEngine engine = BogusSslContextFactory.getInstance().getServerContext().createSSLEngine();
engine.setUseClientMode(false);
pipeline.addLast("ssl", new SslHandler(engine));
}
pipeline.addLast("codec", new HttpServerCodec());
pipeline.addLast("handler", new RequestHandler());
return pipeline;
}
});
channelGroup = new DefaultChannelGroup("hotpotato-upload-server-" + Integer.toHexString(hashCode()));
SocketAddress bindAddress = (host != null) ? new InetSocketAddress(host, port) : new InetSocketAddress(port);
Channel serverChannel = bootstrap.bind(bindAddress);
channelGroup.add(serverChannel);
return (running = serverChannel.isBound());
}
public void terminate() {
if (!running) return;
running = false;
channelGroup.close().awaitUninterruptibly();
bootstrap.releaseExternalResources();
}
// private classes ------------------------------------------------------------------------------------------------
private final class RequestHandler
extends SimpleChannelUpstreamHandler {
private HttpRequest request;
private Channel channel;
private ChannelBuffer buffer;
// SimpleChannelUpstreamHandler -------------------------------------------------------------------------------
@Override public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
channel = e.getChannel();
channelGroup.add(e.getChannel());
}
@Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
if (e.getMessage() instanceof HttpRequest) {
request = (HttpRequest) e.getMessage();
System.out.println(request);
System.out.println(request.getContent().toString(CharsetUtil.UTF_8));
handleRequest();
} else if (e.getMessage() instanceof HttpChunk) {
handleChunk((HttpChunk) e.getMessage());
} else {
System.err.println("Unknown message received: " + e.getMessage().getClass().getSimpleName());
e.getChannel().close();
}
}
@Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
System.err.println("*** Exception caught");
e.getCause().printStackTrace();
if (e.getChannel().isConnected()) e.getChannel().close();
}
// private helpers --------------------------------------------------------------------------------------------
private void handleRequest() {
if (verbose) System.err.println("\n*** Got request\n" + request);
if (!request.isChunked()) {
buffer = request.getContent();
sendFinalResponse();
return;
}
String continueHeader = getHeader(request, Names.EXPECT);
if ((continueHeader != null) &&
Values.CONTINUE.equalsIgnoreCase(continueHeader) &&
(pauseBefore100Continue > 0)) {
System.err.println("*** Pausing before sending 100 continue...");
try { Thread.sleep(pauseBefore100Continue); } catch (InterruptedException ignored) { }
send100Continue();
if (verbose) System.err.println("\n*** Sent 100 continue");
}
}
private void handleChunk(HttpChunk chunk) {
if (verbose) {
System.err.println("*** Got chunk with " + chunk.getContent().readableBytes() + " bytes");
System.err.println(chunk.getContent().toString(CharsetUtil.UTF_8));
}
if (buffer == null) buffer = ChannelBuffers.dynamicBuffer((int) getContentLength(request));
buffer.writeBytes(chunk.getContent());
if (chunk.isLast()) sendFinalResponse();
}
private void send100Continue() {
sendResponse(new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.CONTINUE));
}
private void sendFinalResponse() {
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
if ((buffer != null) && (buffer.readableBytes() > 0)) {
String contentType = getHeader(request, Names.CONTENT_TYPE);
if (contentType == null) contentType = "application/octet-stream";
addHeader(response, Names.CONTENT_TYPE, contentType);
addHeader(response, Names.CONTENT_LENGTH, buffer.readableBytes());
response.setContent(buffer);
}
System.err.println("\n*** Sending response to client\n" + response);
sendResponse(response);
}
private void sendResponse(HttpResponse response) {
addHeader(response, Names.SERVER, "UploadMirrorHttpServer");
if (response.getContent() == null) addHeader(response, Names.CONTENT_LENGTH, 0);
boolean keepAlive = isKeepAlive(request);
ChannelFuture f = channel.write(response);
// Write the response & close the connection after the write operation.
if (!keepAlive) f.addListener(ChannelFutureListener.CLOSE);
}
}
// main -----------------------------------------------------------------------------------------------------------
public static void main(String[] args) {
String host = null;
int port = 8080;
boolean verbose = false;
if (args.length >= 1) host = args[0];
if (args.length >= 2) port = Integer.parseInt(args[1]);
if (args.length >= 3) verbose = ("verbose".equals(args[2]));
final UploadMirrorHttpServer server = new UploadMirrorHttpServer(host, port);
server.verbose = verbose;
if (!server.init()) System.err.println("Failed to bind server to " + (host == null ? '*' : host) + ":" + port);
else System.out.println("Server bound to " + (host == null ? '*' : host) + ":" + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override public void run() { server.terminate(); }
});
}
}