Skip to content

Commit

Permalink
#66 Added a betamax-netty module with a basic proof-of-concept HTTP e…
Browse files Browse the repository at this point in the history
…xchange
  • Loading branch information
robfletcher committed Jul 31, 2013
1 parent 0b7ef95 commit 7c1a0b1
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 1 deletion.
11 changes: 11 additions & 0 deletions betamax-netty/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
dependencies {
compile project(':betamax-core')
compile nettyDependency
}

modifyPom {
project {
name 'Betamax Netty'
description 'The base Netty support classes for Betamax.'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package co.freeside.betamax.proxy.netty;

import io.netty.channel.*;
import io.netty.channel.socket.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.*;

/**
* Configures up a channel to handle HTTP requests and responses.
*/
public class HttpChannelInitializer extends ChannelInitializer<SocketChannel> {

public static final int MAX_CONTENT_LENGTH = 65536;

private final ChannelHandler handler;

public HttpChannelInitializer(ChannelHandler handler) {
this.handler = handler;
}

@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new HttpRequestDecoder())
.addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH))
.addLast(new HttpResponseEncoder())
.addLast(new ChunkedWriteHandler())
.addLast(handler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package co.freeside.betamax.proxy.netty;

import java.net.*;
import io.netty.bootstrap.*;
import io.netty.channel.*;
import io.netty.channel.nio.*;
import io.netty.channel.socket.nio.*;

/**
* A Netty-based implementation of the Betamax proxy server.
*/
public class NettyBetamaxServer {

private final int port;
private final ChannelHandler handler;
private EventLoopGroup group;
private Channel channel;

public NettyBetamaxServer(int port, ChannelHandler handler) {
this.port = port;
this.handler = handler;
}

public InetSocketAddress run() throws Exception {
group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new HttpChannelInitializer(handler))
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);

channel = bootstrap.bind(port).sync().channel();
return (InetSocketAddress) channel.localAddress();
}

public void shutdown() throws InterruptedException {
if (channel != null) channel.close().sync();
if (group != null) group.shutdownGracefully();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package co.freeside.betamax.proxy.netty;

import io.netty.buffer.*;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;

@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
FullHttpRequest request = (FullHttpRequest) msg;
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1,
OK,
request.content()
);
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1,
OK,
Unpooled.copiedBuffer(cause.getClass().getSimpleName() + ": " + cause.getMessage(), CharsetUtil.UTF_8)
);
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package co.freeside.betamax.proxy.netty

import spock.lang.Specification

class NettyServerSpec extends Specification {

void "can serve HTTP responses with Netty"() {
given:
def server = new NettyBetamaxServer(port, new EchoServerHandler())
server.run()

when:
HttpURLConnection connection = new URL("http://localhost:$port/").openConnection()
connection.requestMethod = "POST"
connection.doInput = true
connection.doOutput = true
connection.outputStream.withWriter("UTF-8") { writer ->
writer << message
}
connection.connect()

then:
connection.inputStream.getText("UTF-8") == message

cleanup:
server.shutdown()

where:
port = 5000
message = "O HAI"
}

}
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ allprojects {
junitVersion = '4.10'
spockVersion = '0.7-groovy-1.8'
jettyVersion = '7.3.1.v20110307'
nettyVersion = '4.0.4.Final'

groovyDependency = "org.codehaus.groovy:groovy-all:$groovyVersion"
httpClientDependency = "org.apache.httpcomponents:httpclient:$httpClientVersion"
junitDependency = "junit:junit:$junitVersion"
spockDependency = "org.spockframework:spock-core:$spockVersion"
jettyDependency = "org.eclipse.jetty:jetty-server:$jettyVersion"
// TODO: not sure we need netty-all
nettyDependency = "io.netty:netty-all:$nettyVersion"

publishedModules = [':betamax-core', ':betamax-proxy', ':betamax-httpclient', ':betamax-jetty']
publishedModules = [':betamax-core', ':betamax-proxy', ':betamax-httpclient', ':betamax-jetty', ':betamax-netty']
}
}

Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ include 'betamax-core',
'betamax-proxy',
'betamax-httpclient',
'betamax-jetty',
'betamax-netty',
'betamax-test-support'

rootProject.name = 'betamax'

0 comments on commit 7c1a0b1

Please sign in to comment.