Browse Source

Initial commit, created a basic server based on netty

Abhinav Sarkar 6 years ago
commit
e68434b77b

+ 42
- 0
.gitignore View File

@@ -0,0 +1,42 @@
1
+# use glob syntax.
2
+syntax: glob
3
+*.ser
4
+*.class
5
+*~
6
+*.bak
7
+#*.off
8
+*.old
9
+
10
+# eclipse conf file
11
+.settings
12
+.classpath
13
+.project
14
+.manager
15
+.scala_dependencies
16
+.cache
17
+
18
+# idea
19
+.idea
20
+*.iml
21
+
22
+# building
23
+target
24
+build
25
+null
26
+tmp*
27
+temp*
28
+dist
29
+test-output
30
+build.log
31
+
32
+# other scm
33
+.svn
34
+.CVS
35
+.hg*
36
+
37
+# switch to regexp syntax.
38
+#  syntax: regexp
39
+#  ^\.pc/
40
+
41
+#SHITTY output not in target directory
42
+build.log

+ 110
- 0
pom.xml View File

@@ -0,0 +1,110 @@
1
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3
+	<modelVersion>4.0.0</modelVersion>
4
+	<groupId>net.abhinavsarkar.ircsearch</groupId>
5
+	<artifactId>irc-search</artifactId>
6
+	<version>1.0-SNAPSHOT</version>
7
+	<name>${project.artifactId}</name>
8
+
9
+	<properties>
10
+		<maven.compiler.source>1.6</maven.compiler.source>
11
+		<maven.compiler.target>1.6</maven.compiler.target>
12
+		<encoding>UTF-8</encoding>
13
+		<scala.version>2.10.1</scala.version>
14
+		<project.dependencyDir>${project.build.directory}/dependency</project.dependencyDir>
15
+	</properties>
16
+
17
+	<dependencies>
18
+		<dependency>
19
+			<groupId>org.scala-lang</groupId>
20
+			<artifactId>scala-library</artifactId>
21
+			<version>${scala.version}</version>
22
+		</dependency>
23
+		<dependency>
24
+			<groupId>org.scala-lang</groupId>
25
+			<artifactId>scala-reflect</artifactId>
26
+			<version>${scala.version}</version>
27
+		</dependency>
28
+		<dependency>
29
+			<groupId>io.netty</groupId>
30
+			<artifactId>netty</artifactId>
31
+			<version>4.0.0.Alpha5</version>
32
+			<scope>compile</scope>
33
+		</dependency>
34
+		<dependency>
35
+			<groupId>com.typesafe</groupId>
36
+			<artifactId>scalalogging-slf4j_2.10</artifactId>
37
+			<version>1.0.1</version>
38
+		</dependency>
39
+		<dependency>
40
+			<groupId>ch.qos.logback</groupId>
41
+			<artifactId>logback-classic</artifactId>
42
+			<version>1.0.0</version>
43
+			<scope>runtime</scope>
44
+		</dependency>
45
+
46
+		<!-- Test -->
47
+	</dependencies>
48
+
49
+	<build>
50
+		<sourceDirectory>src/main/scala</sourceDirectory>
51
+		<testSourceDirectory>src/test/scala</testSourceDirectory>
52
+		<plugins>
53
+			<plugin>
54
+				<groupId>org.apache.maven.plugins</groupId>
55
+				<artifactId>maven-dependency-plugin</artifactId>
56
+				<version>2.3</version>
57
+				<executions>
58
+					<execution>
59
+						<id>copy-dependencies</id>
60
+						<phase>package</phase>
61
+						<goals>
62
+							<goal>copy-dependencies</goal>
63
+						</goals>
64
+						<configuration>
65
+							<outputDirectory>${project.dependencyDir}</outputDirectory>
66
+							<includeScope>runtime</includeScope>
67
+							<excludeScope>provided</excludeScope>
68
+							<excludeTypes>pom</excludeTypes>
69
+						</configuration>
70
+					</execution>
71
+				</executions>
72
+			</plugin>
73
+			<plugin>
74
+				<groupId>org.scala-tools</groupId>
75
+				<artifactId>maven-scala-plugin</artifactId>
76
+				<version>2.15.0</version>
77
+				<executions>
78
+					<execution>
79
+						<goals>
80
+							<goal>compile</goal>
81
+							<goal>testCompile</goal>
82
+						</goals>
83
+						<configuration>
84
+							<args>
85
+								<arg>-make:transitive</arg>
86
+								<arg>-dependencyfile</arg>
87
+								<arg>${project.build.directory}/.scala_dependencies</arg>
88
+							</args>
89
+						</configuration>
90
+					</execution>
91
+				</executions>
92
+			</plugin>
93
+			<plugin>
94
+				<groupId>org.apache.maven.plugins</groupId>
95
+				<artifactId>maven-surefire-plugin</artifactId>
96
+				<version>2.6</version>
97
+				<configuration>
98
+					<useFile>false</useFile>
99
+					<disableXmlReport>true</disableXmlReport>
100
+					<!-- If you have classpath issue like NoDefClassError,... -->
101
+					<!-- useManifestOnlyJar>false</useManifestOnlyJar -->
102
+					<includes>
103
+						<include>**/*Test.*</include>
104
+						<include>**/*Suite.*</include>
105
+					</includes>
106
+				</configuration>
107
+			</plugin>
108
+		</plugins>
109
+	</build>
110
+</project>

+ 71
- 0
src/main/scala/net/abhinavsarkar/ircsearch/HttpRequestHandler.scala View File

@@ -0,0 +1,71 @@
1
+package net.abhinavsarkar.ircsearch
2
+
3
+import io.netty.channel.ChannelInboundMessageHandlerAdapter
4
+import io.netty.handler.codec.http.HttpRequest
5
+import com.typesafe.scalalogging.slf4j.Logging
6
+import io.netty.channel.ChannelHandlerContext
7
+import io.netty.handler.codec.http.HttpResponse
8
+import io.netty.channel.ChannelFuture
9
+import io.netty.handler.codec.http.HttpHeaders.isKeepAlive
10
+import io.netty.handler.codec.http.HttpHeaders.Names._
11
+import io.netty.handler.codec.http.HttpHeaders
12
+import io.netty.channel.ChannelFutureListener
13
+import io.netty.handler.codec.http.DefaultHttpResponse
14
+import io.netty.handler.codec.http.HttpResponseStatus._
15
+import io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
16
+import io.netty.handler.codec.http.HttpVersion
17
+import io.netty.handler.codec.http.HttpResponseStatus
18
+import io.netty.buffer.Unpooled
19
+
20
+
21
+trait HttpRequestHandler extends ChannelInboundMessageHandlerAdapter[HttpRequest] with Logging {
22
+
23
+  protected def sendDefaultResponse(ctx : ChannelHandlerContext, request : HttpRequest) : HttpResponse = {
24
+    val response = new DefaultHttpResponse(
25
+        HTTP_1_1, if (request.getDecoderResult.isSuccess) OK else BAD_REQUEST)
26
+    writeResponse(ctx, request, response)
27
+    response
28
+  }
29
+
30
+  protected def sendNotFound(ctx : ChannelHandlerContext, request : HttpRequest) : HttpResponse = {
31
+    val response = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND)
32
+    writeResponse(ctx, request, response)
33
+    response
34
+  }
35
+
36
+  protected def sendSuccess(ctx : ChannelHandlerContext, request : HttpRequest, body : String) : HttpResponse = {
37
+    val response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
38
+    response.setContent(Unpooled.copiedBuffer(body.getBytes))
39
+    writeResponse(ctx, request, response)
40
+    response
41
+  }
42
+
43
+  protected def writeResponse(
44
+      ctx : ChannelHandlerContext, request : HttpRequest, response : HttpResponse) {
45
+    response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes())
46
+
47
+    if (isKeepAlive(request)) {
48
+        response.setHeader(CONNECTION, HttpHeaders.Values.KEEP_ALIVE)
49
+    }
50
+
51
+    val future = ctx.write(response)
52
+
53
+    if (!isKeepAlive(request) || response.getStatus().getCode() != 200) {
54
+        future.addListener(ChannelFutureListener.CLOSE)
55
+    }
56
+  }
57
+
58
+  protected def logRequest(ctx: ChannelHandlerContext, request: HttpRequest, response: HttpResponse) {
59
+    logger.info("{} {} {} {}",
60
+      response.getStatus().getCode() : Integer,
61
+      request.getMethod(),
62
+      request.getUri(),
63
+      ctx.channel().remoteAddress())
64
+  }
65
+
66
+  override def exceptionCaught(ctx : ChannelHandlerContext, cause : Throwable) {
67
+    logger.warn("Exception in handling request", cause)
68
+    ctx.close()
69
+  }
70
+
71
+}

+ 34
- 0
src/main/scala/net/abhinavsarkar/ircsearch/HttpRequestRouter.scala View File

@@ -0,0 +1,34 @@
1
+package net.abhinavsarkar.ircsearch
2
+
3
+import io.netty.channel.ChannelHandler.Sharable
4
+import java.util.regex.Pattern
5
+import io.netty.channel.ChannelHandlerContext
6
+import io.netty.handler.codec.http.HttpRequest
7
+
8
+@Sharable
9
+abstract class HttpRequestRouter extends HttpRequestHandler {
10
+
11
+  def route : PartialFunction[String, HttpRequestHandler]
12
+
13
+  override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
14
+    if (request.getDecoderResult.isSuccess) {
15
+      val uri = request.getUri
16
+      if (route.isDefinedAt(uri)) {
17
+        val routeHandler = route.apply(uri)
18
+        ctx.pipeline.addLast("handler", routeHandler)
19
+        try {
20
+            ctx.nextInboundMessageBuffer.add(request)
21
+            ctx.fireInboundBufferUpdated
22
+        } finally {
23
+            ctx.pipeline.remove("handler")
24
+        }
25
+      } else {
26
+        logRequest(ctx, request, sendNotFound(ctx, request))
27
+      }
28
+    } else {
29
+      logger.warn("Could not decode request: {}", request)
30
+      logRequest(ctx, request, sendDefaultResponse(ctx, request))
31
+    }
32
+  }
33
+
34
+}

+ 81
- 0
src/main/scala/net/abhinavsarkar/ircsearch/Server.scala View File

@@ -0,0 +1,81 @@
1
+package net.abhinavsarkar.ircsearch
2
+
3
+import java.net.InetSocketAddress
4
+import com.typesafe.scalalogging.slf4j.Logging
5
+import io.netty.bootstrap.ServerBootstrap
6
+import io.netty.channel.ChannelHandler.Sharable
7
+import io.netty.channel.ChannelHandlerContext
8
+import io.netty.channel.ChannelInitializer
9
+import io.netty.channel.socket.SocketChannel
10
+import io.netty.channel.socket.nio.NioEventLoopGroup
11
+import io.netty.channel.socket.nio.NioServerSocketChannel
12
+import io.netty.handler.codec.http.HttpChunkAggregator
13
+import io.netty.handler.codec.http.HttpContentCompressor
14
+import io.netty.handler.codec.http.HttpRequest
15
+import io.netty.handler.codec.http.HttpRequestDecoder
16
+import io.netty.handler.codec.http.HttpResponseEncoder
17
+import io.netty.handler.codec.http.DefaultHttpResponse
18
+import io.netty.handler.codec.http.HttpVersion
19
+import io.netty.handler.codec.http.HttpResponseStatus
20
+import io.netty.buffer.Unpooled
21
+import java.nio.charset.Charset
22
+
23
+object Server extends App with Logging {
24
+
25
+  if (args.isEmpty) {
26
+    println("Please specify port to run the server on")
27
+    System.exit(1)
28
+  } else {
29
+    val port = args(0).toInt
30
+    logger.info("Starting server at port {}", port: Integer)
31
+
32
+    val httpRequestRouter = new HttpRequestRouter {
33
+      val Echo = "^/echo$".r
34
+      def route = {
35
+        case Echo() => new HttpRequestHandler {
36
+          override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
37
+            val content = request.getContent().toString(Charset.forName("UTF-8"))
38
+            logRequest(ctx, request, sendSuccess(ctx, request, content))
39
+          }
40
+        }
41
+      }
42
+    }
43
+
44
+    val server = (new ServerBootstrap)
45
+      .group(new NioEventLoopGroup(1), new NioEventLoopGroup(1))
46
+      .channel(classOf[NioServerSocketChannel])
47
+      .childHandler(new ChannelInitializer[SocketChannel] {
48
+        def initChannel(ch: SocketChannel) {
49
+          val p = ch.pipeline
50
+            .addLast("decoder", new HttpRequestDecoder)
51
+            .addLast("aggregator", new HttpChunkAggregator(1048576))
52
+            .addLast("encoder", new HttpResponseEncoder)
53
+            .addLast("compressor", new HttpContentCompressor)
54
+            .addLast("router", httpRequestRouter)
55
+        }})
56
+      .localAddress(new InetSocketAddress(port))
57
+
58
+    Runtime.getRuntime.addShutdownHook(
59
+      new Thread("ShutdownHook") {
60
+        override def run {
61
+          stopServer(server);
62
+        }
63
+      })
64
+
65
+    try {
66
+      server.bind().sync.channel.closeFuture.sync
67
+    } catch {
68
+      case e : Exception => {
69
+        logger.error("Exception while running server. Stopping server", e)
70
+        stopServer(server);
71
+      }
72
+    }
73
+  }
74
+
75
+  def stopServer(server : ServerBootstrap) {
76
+    logger.info("Stopping server")
77
+    server.shutdown
78
+    logger.info("Stopped server")
79
+  }
80
+
81
+}

Loading…
Cancel
Save