Initial commit, created a basic server based on netty
This commit is contained in:
commit
e68434b77b
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal 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
110
pom.xml
Normal 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>
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
81
src/main/scala/net/abhinavsarkar/ircsearch/Server.scala
Normal file
81
src/main/scala/net/abhinavsarkar/ircsearch/Server.scala
Normal 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")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user