Initial commit, created a basic server based on netty
commit
e68434b77b
|
@ -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
|
|
@ -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>
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue