Skip to content

Commit

Permalink
paypal/squbs#12: Completed Scala template.
Browse files Browse the repository at this point in the history
  • Loading branch information
Akara Sucharitakul committed Dec 7, 2015
1 parent 05d827d commit 892db81
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 143 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ Getting Started

1. Run `sbt` from root directory to enter the interactive mode.

2. Start the server by using `run`.
2. Start the server by using `re-start` or `run`.

3. Go to http://localhost:8080/actor
3. Run test coverage by `;clean;coverage;test`.

4. URLs:
* http://localhost:8080/hello: Simple hello response
* http://localhost:8080/hello/{some_name}: Hello response greeting name and return Json response
* http://localhost:8080/hello/{some_name}/{delay}: Sends chunked response in intervals with delay in milliseconds

5. Console URL: http://localhost:8080/adm

Most important - have fun!
--------------------------
10 changes: 7 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


name := "squbs-seed"

version := "0.0.1-SNAPSHOT"
Expand All @@ -12,14 +14,18 @@ resolvers += Resolver.sonatypeRepo("snapshots")

scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-encoding", "utf8", "-language:postfixOps")

val squbsV = "0.7.0"
val squbsV = "0.7.1-SNAPSHOT"

val akkaV = "2.3.13"

Revolver.settings

libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.1.3",
"org.squbs" %% "squbs-unicomplex" % squbsV,
"org.squbs" %% "squbs-actormonitor" % squbsV,
"org.squbs" %% "squbs-httpclient" % squbsV,
"org.squbs" %% "squbs-admin" % squbsV,
"org.squbs" %% "squbs-testkit" % squbsV % "test",
"io.spray" %% "spray-testkit" % "1.3.3" % "test"
)
Expand All @@ -33,8 +39,6 @@ compileScalastyle := org.scalastyle.sbt.ScalastylePlugin.scalastyle.in(Compile).

(compile in Compile) <<= (compile in Compile) dependsOn compileScalastyle

coverageEnabled := true

coverageMinimum := 100

coverageFailOnMinimum := true
2 changes: 2 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
logLevel := Level.Warn

addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2")

addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.7.0")

addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.3")
Expand Down
40 changes: 20 additions & 20 deletions scalastyle-config.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<scalastyle>
<name>Scalastyle standard configuration</name>
<check level="warning" class="org.scalastyle.file.FileTabChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.FileTabChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.file.FileLengthChecker" enabled="true">
<parameters>
<parameter name="maxFileLength"><![CDATA[800]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.file.HeaderMatchesChecker" enabled="true">
<check level="warning" class="org.scalastyle.file.HeaderMatchesChecker" enabled="false">
<parameters>
<parameter name="header"><![CDATA[// Copyright (C) 2011-2012 the original author or authors.
// See the LICENCE.txt file distributed with this work for additional
Expand All @@ -25,9 +25,9 @@
// limitations under the License.]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.SpacesAfterPlusChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.WhitespaceEndOfLineChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.SpacesBeforePlusChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.SpacesAfterPlusChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.file.WhitespaceEndOfLineChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.SpacesBeforePlusChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.file.FileLineLengthChecker" enabled="true">
<parameters>
<parameter name="maxLineLength"><![CDATA[160]]></parameter>
Expand All @@ -49,7 +49,7 @@
<parameter name="regex"><![CDATA[^[a-z][A-Za-z]*$]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.EqualsHashCodeChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.EqualsHashCodeChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.IllegalImportsChecker" enabled="true">
<parameters>
<parameter name="illegalImports"><![CDATA[sun._,java.awt._]]></parameter>
Expand All @@ -65,14 +65,14 @@
<parameter name="ignore"><![CDATA[-1,0,1,2,3]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.ReturnChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.NullChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.NoCloneChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.NoFinalizeChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.CovariantEqualsChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.StructuralTypeChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.ReturnChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.NullChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.NoCloneChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.NoFinalizeChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.CovariantEqualsChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.StructuralTypeChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.file.RegexChecker" enabled="true">
<parameters>
<parameter name="regex"><![CDATA[println]]></parameter>
Expand All @@ -88,12 +88,12 @@
<parameter name="maximum"><![CDATA[10]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.UppercaseLChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.SimplifyBooleanExpressionChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.scalariform.UppercaseLChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.SimplifyBooleanExpressionChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.scalariform.IfBraceChecker" enabled="true">
<parameters>
<parameter name="singleLineAllowed"><![CDATA[true]]></parameter>
<parameter name="doubleLineAllowed"><![CDATA[false]]></parameter>
<parameter name="doubleLineAllowed"><![CDATA[true]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.MethodLengthChecker" enabled="true">
Expand All @@ -111,7 +111,7 @@
<parameter name="maxMethods"><![CDATA[30]]></parameter>
</parameters>
</check>
<check level="warning" class="org.scalastyle.scalariform.PublicMethodsHaveTypeChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.NewLineAtEofChecker" enabled="true"></check>
<check level="warning" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="false"></check>
<check level="warning" class="org.scalastyle.scalariform.PublicMethodsHaveTypeChecker" enabled="false"/>
<check level="warning" class="org.scalastyle.file.NewLineAtEofChecker" enabled="true"/>
<check level="warning" class="org.scalastyle.file.NoNewLineAtEofChecker" enabled="false"/>
</scalastyle>
2 changes: 1 addition & 1 deletion src/main/resources/META-INF/squbs-meta.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cube-name = org.squbs.sample.squbs-seed
cube-version = "0.0.1-SNAPSHOT"
squbs-services = [
{
class-name = org.squbs.sample.SampleSvc
class-name = org.squbs.sample.SampleHttpSvc
web-context = ""
}
]
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ akka {

spray.can.server {
request-timeout = 5 s
}

default-listener {
aliases = [ "admin-listener" ]
}
89 changes: 49 additions & 40 deletions src/main/scala/org/squbs/sample/App.scala
Original file line number Diff line number Diff line change
@@ -1,62 +1,71 @@
package org.squbs.sample

import akka.actor._
import org.squbs.unicomplex.RouteDefinition
import spray.http.HttpHeaders.`Content-Type`
import spray.http._
import spray.routing.Route
import spray.routing._
import Directives._

class SampleSvc extends RouteDefinition {
override def route: Route = path("actor") {ctx =>
// actor path = /user/ + cube-shortname + / + actor name
context.actorSelection("/user/squbs-seed/sample") ! ctx
} ~ {
// otherwise go to default response
complete("Hello world")
}
}

import scala.concurrent.duration._

// Messages for interacting with this app.
case class PingRequest(who: String)
case class PingResponse(message: String)
case class ChunkRequest(who: String, delay: FiniteDuration)
case object ChunkEnd
case object EmptyRequest

/**
* The dispatcher serves as a singleton registered entry point. It creates/manages actors to handle
* the actual request and allows multiple access methods to this service. Only HTTP is shown
* but it would be rather simple to add other access methods like messaging/streams, etc.
* We could use actors with routers or any other method that has a static entry point, instead.
*/
class SampleDispatcher extends Actor with ActorLogging {
override def receive: Receive = {
case ctx: RequestContext =>
context.actorOf(Props[SampleActor]) forward ctx
case request =>
context.actorOf(Props[SampleActor]) forward request
}
}

/**
* This is the actor that handles the request messages.
*/
class SampleActor extends Actor with ActorLogging {
val system = context.system
import system.dispatcher
import system.scheduler
import concurrent.duration._

case class SampleAck(remaining: Int)

override def receive: Receive = {
case ctx:RequestContext =>
val user = ctx.request.uri.query.get("user").getOrElse("there")
val messages = s"Hello $user, welcome to Squbs!".split("")
context.become(chunkResponse(ctx.responder, messages))
// Have to use text/html for some specific browser like Safari
// https://code.google.com/p/chromium/issues/detail?id=156023
ctx.responder ! ChunkedResponseStart(HttpResponse().withHeaders(`Content-Type`(MediaTypes.`text/html`)))
.withAck(SampleAck(messages.length))

case other =>
log.warning(s"Get unexpected message: $other")
context.stop(self)
}

def chunkResponse(responder: ActorRef, messages: Array[String]): Actor.Receive = {
case SampleAck(0) =>
responder ! ChunkedMessageEnd
log.info(s"No remaining messages, stop the actor")
case PingRequest(who) =>
if (who.trim.nonEmpty) sender() ! PingResponse(s"Hello $who welcome to squbs!")
else sender() ! EmptyRequest
context.stop(self)

case SampleAck(remaining) => scheduler.scheduleOnce(50 millis) {
responder ! MessageChunk(messages(messages.length - remaining)).withAck(SampleAck(remaining - 1))
}
case ChunkRequest(who, delay) =>
val requester = sender() // Save the requester for use in the scheduler.
val responses = Iterator("Hello ", who, " welcome ", "to ", "squbs!")
if (delay.toMillis > 0) {
val scheduler = context.system.scheduler.schedule(delay, delay) {
if (responses.hasNext) requester ! PingResponse(responses.next())
else {
requester ! ChunkEnd
self ! ChunkEnd
}
}
context.become(cancelReceive(scheduler))
}
else {
responses foreach { requester ! PingResponse(_) }
requester ! ChunkEnd
context.stop(self)
}

case _ =>
context.stop(self)
}

def cancelReceive(scheduler: Cancellable): Receive = {
case ChunkEnd =>
scheduler.cancel()
context.stop(self)
}
}
Loading

0 comments on commit 892db81

Please sign in to comment.