Initial commit, created a basic server based on netty

master
Abhinav Sarkar 2013-05-13 02:24:14 +05:30
commit e68434b77b
5 changed files with 338 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# use glob syntax.
syntax: glob
*.ser
*.class
*~
*.bak
#*.off
*.old
# eclipse conf file
.settings
.classpath
.project
.manager
.scala_dependencies
.cache
# idea
.idea
*.iml
# building
target
build
null
tmp*
temp*
dist
test-output
build.log
# other scm
.svn
.CVS
.hg*
# switch to regexp syntax.
# syntax: regexp
# ^\.pc/
#SHITTY output not in target directory
build.log

110
pom.xml Normal file
View File

@ -0,0 +1,110 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.abhinavsarkar.ircsearch</groupId>
<artifactId>irc-search</artifactId>
<version>1.0-SNAPSHOT</version>
<name>${project.artifactId}</name>
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.10.1</scala.version>
<project.dependencyDir>${project.build.directory}/dependency</project.dependencyDir>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-reflect</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>4.0.0.Alpha5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>scalalogging-slf4j_2.10</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<!-- Test -->
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.dependencyDir}</outputDirectory>
<includeScope>runtime</includeScope>
<excludeScope>provided</excludeScope>
<excludeTypes>pom</excludeTypes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<version>2.15.0</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-make:transitive</arg>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.6</version>
<configuration>
<useFile>false</useFile>
<disableXmlReport>true</disableXmlReport>
<!-- If you have classpath issue like NoDefClassError,... -->
<!-- useManifestOnlyJar>false</useManifestOnlyJar -->
<includes>
<include>**/*Test.*</include>
<include>**/*Suite.*</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,71 @@
package net.abhinavsarkar.ircsearch
import io.netty.channel.ChannelInboundMessageHandlerAdapter
import io.netty.handler.codec.http.HttpRequest
import com.typesafe.scalalogging.slf4j.Logging
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.HttpResponse
import io.netty.channel.ChannelFuture
import io.netty.handler.codec.http.HttpHeaders.isKeepAlive
import io.netty.handler.codec.http.HttpHeaders.Names._
import io.netty.handler.codec.http.HttpHeaders
import io.netty.channel.ChannelFutureListener
import io.netty.handler.codec.http.DefaultHttpResponse
import io.netty.handler.codec.http.HttpResponseStatus._
import io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.handler.codec.http.HttpVersion
import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.buffer.Unpooled
trait HttpRequestHandler extends ChannelInboundMessageHandlerAdapter[HttpRequest] with Logging {
protected def sendDefaultResponse(ctx : ChannelHandlerContext, request : HttpRequest) : HttpResponse = {
val response = new DefaultHttpResponse(
HTTP_1_1, if (request.getDecoderResult.isSuccess) OK else BAD_REQUEST)
writeResponse(ctx, request, response)
response
}
protected def sendNotFound(ctx : ChannelHandlerContext, request : HttpRequest) : HttpResponse = {
val response = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND)
writeResponse(ctx, request, response)
response
}
protected def sendSuccess(ctx : ChannelHandlerContext, request : HttpRequest, body : String) : HttpResponse = {
val response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
response.setContent(Unpooled.copiedBuffer(body.getBytes))
writeResponse(ctx, request, response)
response
}
protected def writeResponse(
ctx : ChannelHandlerContext, request : HttpRequest, response : HttpResponse) {
response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes())
if (isKeepAlive(request)) {
response.setHeader(CONNECTION, HttpHeaders.Values.KEEP_ALIVE)
}
val future = ctx.write(response)
if (!isKeepAlive(request) || response.getStatus().getCode() != 200) {
future.addListener(ChannelFutureListener.CLOSE)
}
}
protected def logRequest(ctx: ChannelHandlerContext, request: HttpRequest, response: HttpResponse) {
logger.info("{} {} {} {}",
response.getStatus().getCode() : Integer,
request.getMethod(),
request.getUri(),
ctx.channel().remoteAddress())
}
override def exceptionCaught(ctx : ChannelHandlerContext, cause : Throwable) {
logger.warn("Exception in handling request", cause)
ctx.close()
}
}

View File

@ -0,0 +1,34 @@
package net.abhinavsarkar.ircsearch
import io.netty.channel.ChannelHandler.Sharable
import java.util.regex.Pattern
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.HttpRequest
@Sharable
abstract class HttpRequestRouter extends HttpRequestHandler {
def route : PartialFunction[String, HttpRequestHandler]
override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
if (request.getDecoderResult.isSuccess) {
val uri = request.getUri
if (route.isDefinedAt(uri)) {
val routeHandler = route.apply(uri)
ctx.pipeline.addLast("handler", routeHandler)
try {
ctx.nextInboundMessageBuffer.add(request)
ctx.fireInboundBufferUpdated
} finally {
ctx.pipeline.remove("handler")
}
} else {
logRequest(ctx, request, sendNotFound(ctx, request))
}
} else {
logger.warn("Could not decode request: {}", request)
logRequest(ctx, request, sendDefaultResponse(ctx, request))
}
}
}

View File

@ -0,0 +1,81 @@
package net.abhinavsarkar.ircsearch
import java.net.InetSocketAddress
import com.typesafe.scalalogging.slf4j.Logging
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelHandler.Sharable
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.codec.http.HttpChunkAggregator
import io.netty.handler.codec.http.HttpContentCompressor
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpRequestDecoder
import io.netty.handler.codec.http.HttpResponseEncoder
import io.netty.handler.codec.http.DefaultHttpResponse
import io.netty.handler.codec.http.HttpVersion
import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.buffer.Unpooled
import java.nio.charset.Charset
object Server extends App with Logging {
if (args.isEmpty) {
println("Please specify port to run the server on")
System.exit(1)
} else {
val port = args(0).toInt
logger.info("Starting server at port {}", port: Integer)
val httpRequestRouter = new HttpRequestRouter {
val Echo = "^/echo$".r
def route = {
case Echo() => new HttpRequestHandler {
override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
val content = request.getContent().toString(Charset.forName("UTF-8"))
logRequest(ctx, request, sendSuccess(ctx, request, content))
}
}
}
}
val server = (new ServerBootstrap)
.group(new NioEventLoopGroup(1), new NioEventLoopGroup(1))
.channel(classOf[NioServerSocketChannel])
.childHandler(new ChannelInitializer[SocketChannel] {
def initChannel(ch: SocketChannel) {
val p = ch.pipeline
.addLast("decoder", new HttpRequestDecoder)
.addLast("aggregator", new HttpChunkAggregator(1048576))
.addLast("encoder", new HttpResponseEncoder)
.addLast("compressor", new HttpContentCompressor)
.addLast("router", httpRequestRouter)
}})
.localAddress(new InetSocketAddress(port))
Runtime.getRuntime.addShutdownHook(
new Thread("ShutdownHook") {
override def run {
stopServer(server);
}
})
try {
server.bind().sync.channel.closeFuture.sync
} catch {
case e : Exception => {
logger.error("Exception while running server. Stopping server", e)
stopServer(server);
}
}
}
def stopServer(server : ServerBootstrap) {
logger.info("Stopping server")
server.shutdown
logger.info("Stopped server")
}
}