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