Sept 06 2014
Ngoc Dao 
https://github.com/ngocdaothanh 
Takeharu Oshida 
https://github.com/georgeOsdDev 
http://mobilus.co.jp/
What is 
Xitrum? 
Xitrum is an async and clustered ! 
Scala web framework and HTTP(S) server ! 
on top of Netty, Akka
Why you should use 
Xitrum? 
• Featureful! 
• Easy to use! 
• High performance 
Scala, Netty, and Akka are fast! 
• Scalable 
Can scale to a cluster of servers using 
Akka cluster and/or Hazelcast
Homepage: 
http://xitrum-framework.github.io/ 
(there are various demos) 
Guides (English, Japanese, Russian): 
http://xitrum-framework.github.io/guide.html 
(Korean version is in progress) 
! 
Community (Google Group): 
https://groups.google.com/forum/#!forum/ 
xitrum-framework
Where 
Xitrum is used? 
KONNECT (Messaging Service)! 
http://mobilus.co.jp/konnect/! 
! 
KONNECT can be used in mobile games, 
mobiles apps, SNS websites etc. 
Xitrum is also being used in France, Korea, 
Russia, Singapore etc.
Xitrum:! 
WebSocket (SockJS)! 
CORS support
2010-2013 Xitrum 1.x-2.x 
http://bit.ly/xitrum13 
2014 Xitrum 3.x 
• Netty 4.x 
• Swagger 
• Component 
• FileMonitor, i18n 
• CORS 
• WebJARs 
• Glokka 
• Agent7 (autoreload classes on change) 
! 
! 
http://bit.ly/xitrum-changelog 
!
How Xitrum 
works?
Client 
Netty 
Async 
Dispatch 
request 
Action FutureAction ActorAction 
Akka 
Xitrum 
I/O thread pool to 
accept requests and reply 
responses 
Thread pool 
to run FutureAction and 
ActorAction 
Client 
Run directly on 
Netty I/O thread 
Netty handler 
Netty handler 
Netty handler 
Netty handler 
Xitrum 
Your program
http://bit.ly/xitrum-handlers
Client 
Client Client 
Akka cluster (code)! 
Hazelcast (data) 
Client 
Netty 
Xitrum 
A FA AA 
Akka 
Netty 
Xitrum 
A FA AA 
Akka 
Server N Server N+1
Embed Xitrum 
object MyApp { 
def main(args: Array[String]) { 
... 
// Somewhere in your app 
xitrum.Server.start() 
... 
} 
1. Collect routes! 
} 
! 
2. Start HTTP/HTTPS servers
Action example 
import xitrum.Action 
import xitrum.annotation.GET 
! 
@GET("hello") 
class MyAction extends Action { 
def execute() { 
respondText("Hello") 
} 
} 
FutureAction! 
ActorAction
Annotations: Scala vs Java 
Scala: @GET("matsuri", "festival") 
Java: @GET(Array("matsuri", "festival")) 
! 
Scala: 
case class GET(paths: String*) extends 
scala.annotation.StaticAnnotation 
! 
Java: 
public @interface GET { 
String[] value(); 
}
Benefits of using annotations 
Routes in .class and .jar in classpath are 
automatically collected and merged. 
A.class 
B.class 
lib1.jar 
lib2.jar 
Routes
Problem with annotations 
Collecting routes from .class and .jar files is slow.! 
! 
Solutions:! 
• In development mode, routes in .class and .jar 
files that are not in the current working directory 
are cached to file routes.cache. 
• To avoid loading lots of classes, don't collect 
routes from: java.xxx, javax.xxx, scala.xxx, 
sun.xxx, com.sun.xxx
Annotations defined by Xitrum:! 
http://bit.ly/xitrum-annotations! 
! 
Lib to scan classes in classpath to collect routes:! 
https://github.com/xitrum-framework/sclasner
Demo overview 
GET / GET /chat 
username 
password 
login 
Hello 
! 
Hello! 
How are you? 
Fine 
message send 
POST /login SockJS /connect 
https://github.com/xitrum-framework/matsuri14
Demo overview 
• Simple HTTP CRUD with MongoDB 
POST /admin/user 
GET /admin/user 
GET /admin/user/:userId 
PUT /admin/user/:userId 
DELETE /admin/user/:userId 
PUT/PATCH/DELETE can be emulated via 
POST with _method=PUT/PATCH/DELETE 
! 
• API documentation with Swagger
DB Cluseter 
Xitrum1 Akka Hazelcast 
Xitrum2 Akka Hazelcast 
Xitrum3 
Cluster 
LB 
(HAProxy, Nginx, 
Route53 etc.) 
NoSQL 
RDB 
Other services 
Akka Hazelcast 
https://github.com/xitrum-framework/glokka 
https://github.com/xitrum-framework/xitrum-hazelcast
Getting started 
with xitrum-new 
skeleton 
https://github.com/xitrum-framework/xitrum-new 
https://github.com/xitrum-framework/xitrum-scalate
@GET("admin") 
class AdminIndex extends Action { 
def execute() { 
// Get all users 
val users = User.listAll() 
// Pass users to view template 
at("users") = users 
! 
// Response respons view with template 
respondView() 
} 
} 
ActorAction 
FutureAction 
Action
- import matsuri.demo.action._! 
- import matsuri.demo.model.User! 
! 
div.row#usersTable! 
table.table.table-striped#messageTable! 
thead! 
tr.bg-primary! 
View 
• Scalate template with jade 
(mustache, scaml, or ssp) 
• "at" function 
• i18n with GNU gettext 
th.col-xs-2 =t("Name")! 
th.col-xs-2 =t("Age")! 
th.col-xs-2 =t("Desc")! 
th.col-xs-2 =t("Created time")! 
th.col-xs-2 =t("Updated time")! 
th.col-xs-2 =t("Last login time")! 
tbody! 
- for (user <- at("users").asInstanceOf[List[User]])! 
tr! 
th! 
a(href={url[AdminUserShow](("name", user.name))}) = user.name! 
th = user.age! 
th = user.desc! 
th = user.createdAtAsStr! 
th = user.updatedAtAsStr! 
th = user.lastLoginAsStr
└── src 
└── scalate 
└── matsuri 
└── demo 
└── action 
├── AdminIndex.jade 
└── DefaultLayout.jade 
package matsuri.demo.action 
! 
import xitrum.Action 
! 
trait DefaultLayout extends Action { 
override def layout = 
renderViewNoLayout[DefaultLayout]() 
} 
Layout
!!! 5 
html 
head 
!= antiCsrfMeta 
!= xitrumCss 
! 
meta(content="text/html; charset=utf-8" http-equiv="content-type") 
title ScalaMatsuri2014 Xitrum Demo 
! 
link(rel="shortcut icon" href={publicUrl("favicon.ico")}) 
link(type="text/css" rel="stylesheet" media="all" href={webJarsUrl("bootstrap/3.2.0/css", 
"bootstrap.css", "bootstrap.min.css")}) 
link(type="text/css" rel="stylesheet" media="all" href={publicUrl("app.css")}) 
! 
body 
.container 
h1 
! 
#flash 
!~ jsRenderFlash() 
!= renderedView 
Layout 
! 
!= jsDefaults 
script(src={webJarsUrl("bootstrap/3.2.0/js", "bootstrap.js", "bootstrap.min.js")}) 
script(src={webJarsUrl("underscorejs/1.6.0", "underscore.js", "underscore-min.js")}) 
!= jsForView
form(role="form" method="post" action={url[AdminUserCreate]}) 
!= antiCsrfInput 
div.modal-header 
button.close(type="button" data-dismiss="modal") 
Form 
span(aria-hidden="true") &times; 
span.sr-only =t("Close") 
h4.modal-title#myModalLabel =t("Create New User") 
div.modal-body 
div.form-group 
label(for="newUserName") =t("Name") 
input.form-control#newUserName(name="name" type="text" 
• "url" function 
• jquery-validation 
• Anti csrf token 
placeholder={t("Enter Name")} minlength=5 maxlenght=10 required=true) 
div.form-group 
label(for="newUserPass") =t("Password") 
input.form-control#newUserPass(name="password" type="password" 
placeholder={t("Enter Password")} minlength=8 required=true) 
! 
div.modal-footer 
button.btn.btn-default(type="button" data-dismiss="modal") = t("Cancel") 
button.btn.btn-primary(type="submit") = t("Save")
@POST("admin/user") 
class AdminUserCreate extends AdminAction { 
def execute() { 
// Get request paramaters 
val name = param("name") 
val password = param("password") 
// Optional parameters 
val age = paramo[Int]("age") 
val desc = paramo("desc") 
! 
Required.exception("name", name) 
Required.exception("password", password) 
! 
User.create(name, password, age, desc) 
flash(t("Success")) 
redirectTo[AdminIndex]() 
} 
Get 
request 
params 
with 
param(s) 
and 
param(o)
object SVar { 
object isAdmin extends SessionVar[Boolean] 
} 
Use before filter 
to check 
trait AdminFilter { 
this: Action => 
! 
beforeFilter { 
if (SVar.isAdmin.isDefined) true else authBasic() 
} 
! 
private def authBasic(): Boolean = { 
basicAuth(Config.basicAuth.realm) { (username, password) => 
if (username == Config.basicAuth.name && password == Config.basicAuth.pass) { 
SVar.isAdmin.set(true) 
true 
} else { 
false 
} 
} 
} 
authentication 
info in session
@Swagger( 
Swagger.Summary("Create User"), 
Swagger.Response(200, "status = 0: success, 1: failed to create user"), 
Swagger.Response(400, "Invalid request parameter"), 
Swagger.StringForm("name"), 
Swagger.StringForm("password"), 
Swagger.OptIntForm("age"), 
Swagger.OptStringForm("desc") 
) 
API 
doc 
• /xitrum/swagger 
• /xitrum/swagger-ui 
• Create test client with Swagger-codegen 
https://github.com/wordnik/swagger-ui 
https://github.com/wordnik/swagger-codegen 
https://github.com/wordnik/swagger-spec
@GET("login", "") 
class LoginIndex extends DefaultLayout { 
def execute() { 
respondView() 
} 
} 
! 
@POST("login") 
class Login extends Action { 
def execute() { 
session.clear() 
val name = param("name") 
val password = param("password") 
! 
User.authLogin(name, password) match { 
case Some(user) => 
SVar.userName.set(user.name) 
redirectTo[ChatIndex]() 
! 
case None => 
flash(t(s"Invalid username or password")) 
redirectTo[LoginIndex]() 
} 
} 
} 
Login
jsAddToView( 
"var url = '" + sockJsUrl[ChatAction] + "';" + 
""" 
var socket; 
var initSocket = function() { 
socket = new SockJS(url); 
socket.onopen = function(event) { 
console.log("socket onopen", event.data); 
socket.send(JSON.parse({"msg":"Hello Xitrum"})); 
}; 
socket.onclose = function(event) {console.log("socket onclose", event.data);}; 
socket.onmessage = function(event) {console.log("socket onmessage", event.data);}; 
}; 
initSocket(); 
""" 
) 
!= jsDefaults 
script(src={webJarsUrl("bootstrap/3.2.0/js", "bootstrap.js", "bootstrap.min.js")}) 
script(src={webJarsUrl("underscorejs/1.6.0", "underscore.js", "underscore-min.js")}) 
!= jsForView 
• jsAddToView/jsForView 
• sockJsUrl 
• webJarsUrl 
Create 
chat 
client 
with 
SockJS
import xitrum.{SockJsAction, SockJsText} 
import xitrum.annotation.SOCKJS 
! 
@SOCKJS("connect") 
class ChatAction extends SockJsAction with LoginFilter { 
def execute() { 
context.become { 
SockJsAction 
(an Actor) 
case SockJsText(text) => 
SeriDeseri.fromJson[Map[String, String]](text) match { 
case Some(jsonMap) => 
// echo 
respondSockJsText(SeriDeseri.toJson(jsonMap)) 
case None => 
log.warn(s"Failed to parse request: $text") 
respondSockJsText("invalid request") 
} 
Create 
} 
• SockJsAction 
• SockJsText/respondSockJsText 
• SeriDeseri.fromJson[T] / SeriDeseri.toJson(ref:AnyRef)
Lookup 
singleton 
Actor 
with 
Glokka 
Xitrum 
socket open 
Client 
HubActor 
trait Hub extends Actor { 
protected var clients = Seq[ActorRef]() 
def receive = { 
case Subscribe(option) => 
clients = clients :+ sender 
case Unsubscribe(option) => 
clients = clients.filterNot(_ == sender) 
case Terminated(client) => 
clients = clients.filterNot(_ == client) 
case ignore => 
} 
import glokka.Registry 
object Hub { 
val KEY_PROXY = "HUB_PROXY" 
val actorRegistry = Registry.start(Config.actorSystem, 
KEY_PROXY) 
} 
! 
def lookUpHub(key: String, hubProps: Props, option: Any = None) 
{ 
Hub.actorRegistry ! Registry.Register(key, hubProps) 
context.become { 
hub ! Subscribe 
ChatAction ChatAction ChatAction ChatAction 
case result: Registry.FoundOrCreated => 
result.ref ! Subscribe 
} 
} 
Client Client Client 
https://github.com/xitrum-framework/glokka
Messaging overview 
SockJsText! 
(socket.send) hub ! Push(msg) 
clients.foreach { _ ! Publish(msg)} respondSockJSText(msg:String) 
Client ChatAction HubActor 
socket.onmessage 
SockJsText hub ! Pull(msg) 
case class Done (option: Map[String, Any] = Map.empty) // Hub -> Action 
case class Publish(option: Map[String, Any] = Map.empty) // Hub -> Action 
case class Pull (option: Map[String, Any] = Map.empty) // Action -> Hub 
case class Push (option: Map[String, Any] = Map.empty) // Action -> Hub 
https://github.com/georgeOsdDev/glokka-demo 
ChatAction Client 
ChatAction 
ChatAction 
Client 
Client 
respondSockJSText(msg:String) sender ! Done(msg) 
Client ChatAction HubActor 
respondSockJSText(msg:String) sender ! Done(msg) 
socket.onmessage
Cluster config for Hazelcast 
hazelcastMode = clusterMember 
! 
cache = xitrum.hazelcast.Cache 
#cache { 
# # Simple in-memory cache 
# "xitrum.local.LruCache" { 
xitrum.conf 
# maxElems = 10000 
# } 
• Xitrum-hazelcast 
#} 
• Shared Session 
• Shared Cache 
! 
session { 
store = xitrum.hazelcast.Session 
# Store sessions on client side 
#store = xitrum.scope.session.CookieSessionStore
akka { 
loggers = ["akka.event.slf4j.Slf4jLogger"] 
logger-startup-timeout = 30s 
! 
actor { 
provider = "akka.cluster.ClusterActorRefProvider" 
} 
! 
# This node 
remote { 
log-remote-lifecycle-events = off 
netty.tcp { 
hostname = "127.0.0.1" 
port = 2551 # 0 means random port 
} 
} 
! 
cluster { 
seed-nodes = [ 
"akka.tcp://xitrum@127.0.0.1:2551", 
"akka.tcp://xitrum@127.0.0.1:2552"] 
! 
auto-down-unreachable-after = 10s 
} 
} 
Cluster 
config 
for 
Akka
Live class reload 
during development 
https://github.com/xitrum-framework/agent7 
https://github.com/dcevm/dcevm 
java -javaagent:`dirname $0`/agent7-1.0.jar 
-XXaltjvm=dcevm -Xms256M -Xmx512M -Xss1M 
-XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M 
-jar `dirname $0`/sbt-launch-0.13.5.jar "$@" 
https://github.com/xitrum-framework/xitrum-package
Package project for 
deploying to 
production server 
https://github.com/xitrum-framework/xitrum-package 
sbt/sbt xitrum-package
Monitor Xitrum 
in production mode 
• Scalive 
• Metrics 
• Log to fluentd 
https://github.com/xitrum-framework/scalive 
http://www.slideshare.net/georgeosd/scalive 
http://xitrum-framework.github.io/guide/3.18/en/ 
log.html#log-to-fluentd
Thank 
you!

Xitrum Web Framework Live Coding Demos / Xitrum Web Framework ライブコーディング

  • 1.
  • 2.
    Ngoc Dao https://github.com/ngocdaothanh Takeharu Oshida https://github.com/georgeOsdDev http://mobilus.co.jp/
  • 3.
    What is Xitrum? Xitrum is an async and clustered ! Scala web framework and HTTP(S) server ! on top of Netty, Akka
  • 4.
    Why you shoulduse Xitrum? • Featureful! • Easy to use! • High performance Scala, Netty, and Akka are fast! • Scalable Can scale to a cluster of servers using Akka cluster and/or Hazelcast
  • 5.
    Homepage: http://xitrum-framework.github.io/ (thereare various demos) Guides (English, Japanese, Russian): http://xitrum-framework.github.io/guide.html (Korean version is in progress) ! Community (Google Group): https://groups.google.com/forum/#!forum/ xitrum-framework
  • 6.
    Where Xitrum isused? KONNECT (Messaging Service)! http://mobilus.co.jp/konnect/! ! KONNECT can be used in mobile games, mobiles apps, SNS websites etc. Xitrum is also being used in France, Korea, Russia, Singapore etc.
  • 7.
  • 8.
    2010-2013 Xitrum 1.x-2.x http://bit.ly/xitrum13 2014 Xitrum 3.x • Netty 4.x • Swagger • Component • FileMonitor, i18n • CORS • WebJARs • Glokka • Agent7 (autoreload classes on change) ! ! http://bit.ly/xitrum-changelog !
  • 9.
  • 10.
    Client Netty Async Dispatch request Action FutureAction ActorAction Akka Xitrum I/O thread pool to accept requests and reply responses Thread pool to run FutureAction and ActorAction Client Run directly on Netty I/O thread Netty handler Netty handler Netty handler Netty handler Xitrum Your program
  • 11.
  • 12.
    Client Client Client Akka cluster (code)! Hazelcast (data) Client Netty Xitrum A FA AA Akka Netty Xitrum A FA AA Akka Server N Server N+1
  • 13.
    Embed Xitrum objectMyApp { def main(args: Array[String]) { ... // Somewhere in your app xitrum.Server.start() ... } 1. Collect routes! } ! 2. Start HTTP/HTTPS servers
  • 14.
    Action example importxitrum.Action import xitrum.annotation.GET ! @GET("hello") class MyAction extends Action { def execute() { respondText("Hello") } } FutureAction! ActorAction
  • 15.
    Annotations: Scala vsJava Scala: @GET("matsuri", "festival") Java: @GET(Array("matsuri", "festival")) ! Scala: case class GET(paths: String*) extends scala.annotation.StaticAnnotation ! Java: public @interface GET { String[] value(); }
  • 16.
    Benefits of usingannotations Routes in .class and .jar in classpath are automatically collected and merged. A.class B.class lib1.jar lib2.jar Routes
  • 17.
    Problem with annotations Collecting routes from .class and .jar files is slow.! ! Solutions:! • In development mode, routes in .class and .jar files that are not in the current working directory are cached to file routes.cache. • To avoid loading lots of classes, don't collect routes from: java.xxx, javax.xxx, scala.xxx, sun.xxx, com.sun.xxx
  • 18.
    Annotations defined byXitrum:! http://bit.ly/xitrum-annotations! ! Lib to scan classes in classpath to collect routes:! https://github.com/xitrum-framework/sclasner
  • 19.
    Demo overview GET/ GET /chat username password login Hello ! Hello! How are you? Fine message send POST /login SockJS /connect https://github.com/xitrum-framework/matsuri14
  • 20.
    Demo overview •Simple HTTP CRUD with MongoDB POST /admin/user GET /admin/user GET /admin/user/:userId PUT /admin/user/:userId DELETE /admin/user/:userId PUT/PATCH/DELETE can be emulated via POST with _method=PUT/PATCH/DELETE ! • API documentation with Swagger
  • 21.
    DB Cluseter Xitrum1Akka Hazelcast Xitrum2 Akka Hazelcast Xitrum3 Cluster LB (HAProxy, Nginx, Route53 etc.) NoSQL RDB Other services Akka Hazelcast https://github.com/xitrum-framework/glokka https://github.com/xitrum-framework/xitrum-hazelcast
  • 23.
    Getting started withxitrum-new skeleton https://github.com/xitrum-framework/xitrum-new https://github.com/xitrum-framework/xitrum-scalate
  • 24.
    @GET("admin") class AdminIndexextends Action { def execute() { // Get all users val users = User.listAll() // Pass users to view template at("users") = users ! // Response respons view with template respondView() } } ActorAction FutureAction Action
  • 25.
    - import matsuri.demo.action._! - import matsuri.demo.model.User! ! div.row#usersTable! table.table.table-striped#messageTable! thead! tr.bg-primary! View • Scalate template with jade (mustache, scaml, or ssp) • "at" function • i18n with GNU gettext th.col-xs-2 =t("Name")! th.col-xs-2 =t("Age")! th.col-xs-2 =t("Desc")! th.col-xs-2 =t("Created time")! th.col-xs-2 =t("Updated time")! th.col-xs-2 =t("Last login time")! tbody! - for (user <- at("users").asInstanceOf[List[User]])! tr! th! a(href={url[AdminUserShow](("name", user.name))}) = user.name! th = user.age! th = user.desc! th = user.createdAtAsStr! th = user.updatedAtAsStr! th = user.lastLoginAsStr
  • 26.
    └── src └──scalate └── matsuri └── demo └── action ├── AdminIndex.jade └── DefaultLayout.jade package matsuri.demo.action ! import xitrum.Action ! trait DefaultLayout extends Action { override def layout = renderViewNoLayout[DefaultLayout]() } Layout
  • 27.
    !!! 5 html head != antiCsrfMeta != xitrumCss ! meta(content="text/html; charset=utf-8" http-equiv="content-type") title ScalaMatsuri2014 Xitrum Demo ! link(rel="shortcut icon" href={publicUrl("favicon.ico")}) link(type="text/css" rel="stylesheet" media="all" href={webJarsUrl("bootstrap/3.2.0/css", "bootstrap.css", "bootstrap.min.css")}) link(type="text/css" rel="stylesheet" media="all" href={publicUrl("app.css")}) ! body .container h1 ! #flash !~ jsRenderFlash() != renderedView Layout ! != jsDefaults script(src={webJarsUrl("bootstrap/3.2.0/js", "bootstrap.js", "bootstrap.min.js")}) script(src={webJarsUrl("underscorejs/1.6.0", "underscore.js", "underscore-min.js")}) != jsForView
  • 28.
    form(role="form" method="post" action={url[AdminUserCreate]}) != antiCsrfInput div.modal-header button.close(type="button" data-dismiss="modal") Form span(aria-hidden="true") &times; span.sr-only =t("Close") h4.modal-title#myModalLabel =t("Create New User") div.modal-body div.form-group label(for="newUserName") =t("Name") input.form-control#newUserName(name="name" type="text" • "url" function • jquery-validation • Anti csrf token placeholder={t("Enter Name")} minlength=5 maxlenght=10 required=true) div.form-group label(for="newUserPass") =t("Password") input.form-control#newUserPass(name="password" type="password" placeholder={t("Enter Password")} minlength=8 required=true) ! div.modal-footer button.btn.btn-default(type="button" data-dismiss="modal") = t("Cancel") button.btn.btn-primary(type="submit") = t("Save")
  • 29.
    @POST("admin/user") class AdminUserCreateextends AdminAction { def execute() { // Get request paramaters val name = param("name") val password = param("password") // Optional parameters val age = paramo[Int]("age") val desc = paramo("desc") ! Required.exception("name", name) Required.exception("password", password) ! User.create(name, password, age, desc) flash(t("Success")) redirectTo[AdminIndex]() } Get request params with param(s) and param(o)
  • 30.
    object SVar { object isAdmin extends SessionVar[Boolean] } Use before filter to check trait AdminFilter { this: Action => ! beforeFilter { if (SVar.isAdmin.isDefined) true else authBasic() } ! private def authBasic(): Boolean = { basicAuth(Config.basicAuth.realm) { (username, password) => if (username == Config.basicAuth.name && password == Config.basicAuth.pass) { SVar.isAdmin.set(true) true } else { false } } } authentication info in session
  • 31.
    @Swagger( Swagger.Summary("Create User"), Swagger.Response(200, "status = 0: success, 1: failed to create user"), Swagger.Response(400, "Invalid request parameter"), Swagger.StringForm("name"), Swagger.StringForm("password"), Swagger.OptIntForm("age"), Swagger.OptStringForm("desc") ) API doc • /xitrum/swagger • /xitrum/swagger-ui • Create test client with Swagger-codegen https://github.com/wordnik/swagger-ui https://github.com/wordnik/swagger-codegen https://github.com/wordnik/swagger-spec
  • 32.
    @GET("login", "") classLoginIndex extends DefaultLayout { def execute() { respondView() } } ! @POST("login") class Login extends Action { def execute() { session.clear() val name = param("name") val password = param("password") ! User.authLogin(name, password) match { case Some(user) => SVar.userName.set(user.name) redirectTo[ChatIndex]() ! case None => flash(t(s"Invalid username or password")) redirectTo[LoginIndex]() } } } Login
  • 33.
    jsAddToView( "var url= '" + sockJsUrl[ChatAction] + "';" + """ var socket; var initSocket = function() { socket = new SockJS(url); socket.onopen = function(event) { console.log("socket onopen", event.data); socket.send(JSON.parse({"msg":"Hello Xitrum"})); }; socket.onclose = function(event) {console.log("socket onclose", event.data);}; socket.onmessage = function(event) {console.log("socket onmessage", event.data);}; }; initSocket(); """ ) != jsDefaults script(src={webJarsUrl("bootstrap/3.2.0/js", "bootstrap.js", "bootstrap.min.js")}) script(src={webJarsUrl("underscorejs/1.6.0", "underscore.js", "underscore-min.js")}) != jsForView • jsAddToView/jsForView • sockJsUrl • webJarsUrl Create chat client with SockJS
  • 34.
    import xitrum.{SockJsAction, SockJsText} import xitrum.annotation.SOCKJS ! @SOCKJS("connect") class ChatAction extends SockJsAction with LoginFilter { def execute() { context.become { SockJsAction (an Actor) case SockJsText(text) => SeriDeseri.fromJson[Map[String, String]](text) match { case Some(jsonMap) => // echo respondSockJsText(SeriDeseri.toJson(jsonMap)) case None => log.warn(s"Failed to parse request: $text") respondSockJsText("invalid request") } Create } • SockJsAction • SockJsText/respondSockJsText • SeriDeseri.fromJson[T] / SeriDeseri.toJson(ref:AnyRef)
  • 35.
    Lookup singleton Actor with Glokka Xitrum socket open Client HubActor trait Hub extends Actor { protected var clients = Seq[ActorRef]() def receive = { case Subscribe(option) => clients = clients :+ sender case Unsubscribe(option) => clients = clients.filterNot(_ == sender) case Terminated(client) => clients = clients.filterNot(_ == client) case ignore => } import glokka.Registry object Hub { val KEY_PROXY = "HUB_PROXY" val actorRegistry = Registry.start(Config.actorSystem, KEY_PROXY) } ! def lookUpHub(key: String, hubProps: Props, option: Any = None) { Hub.actorRegistry ! Registry.Register(key, hubProps) context.become { hub ! Subscribe ChatAction ChatAction ChatAction ChatAction case result: Registry.FoundOrCreated => result.ref ! Subscribe } } Client Client Client https://github.com/xitrum-framework/glokka
  • 36.
    Messaging overview SockJsText! (socket.send) hub ! Push(msg) clients.foreach { _ ! Publish(msg)} respondSockJSText(msg:String) Client ChatAction HubActor socket.onmessage SockJsText hub ! Pull(msg) case class Done (option: Map[String, Any] = Map.empty) // Hub -> Action case class Publish(option: Map[String, Any] = Map.empty) // Hub -> Action case class Pull (option: Map[String, Any] = Map.empty) // Action -> Hub case class Push (option: Map[String, Any] = Map.empty) // Action -> Hub https://github.com/georgeOsdDev/glokka-demo ChatAction Client ChatAction ChatAction Client Client respondSockJSText(msg:String) sender ! Done(msg) Client ChatAction HubActor respondSockJSText(msg:String) sender ! Done(msg) socket.onmessage
  • 37.
    Cluster config forHazelcast hazelcastMode = clusterMember ! cache = xitrum.hazelcast.Cache #cache { # # Simple in-memory cache # "xitrum.local.LruCache" { xitrum.conf # maxElems = 10000 # } • Xitrum-hazelcast #} • Shared Session • Shared Cache ! session { store = xitrum.hazelcast.Session # Store sessions on client side #store = xitrum.scope.session.CookieSessionStore
  • 38.
    akka { loggers= ["akka.event.slf4j.Slf4jLogger"] logger-startup-timeout = 30s ! actor { provider = "akka.cluster.ClusterActorRefProvider" } ! # This node remote { log-remote-lifecycle-events = off netty.tcp { hostname = "127.0.0.1" port = 2551 # 0 means random port } } ! cluster { seed-nodes = [ "akka.tcp://xitrum@127.0.0.1:2551", "akka.tcp://xitrum@127.0.0.1:2552"] ! auto-down-unreachable-after = 10s } } Cluster config for Akka
  • 39.
    Live class reload during development https://github.com/xitrum-framework/agent7 https://github.com/dcevm/dcevm java -javaagent:`dirname $0`/agent7-1.0.jar -XXaltjvm=dcevm -Xms256M -Xmx512M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M -jar `dirname $0`/sbt-launch-0.13.5.jar "$@" https://github.com/xitrum-framework/xitrum-package
  • 40.
    Package project for deploying to production server https://github.com/xitrum-framework/xitrum-package sbt/sbt xitrum-package
  • 41.
    Monitor Xitrum inproduction mode • Scalive • Metrics • Log to fluentd https://github.com/xitrum-framework/scalive http://www.slideshare.net/georgeosd/scalive http://xitrum-framework.github.io/guide/3.18/en/ log.html#log-to-fluentd
  • 42.