Rogach meets SBT, or "How to add a remote JavaScript console to your Scala server"
I recently installed sbt, and it was a big breakthrough for my development - before you try things like sbt, you think like "who needs that automatic dependency management", "I can do full recompile on every run - computer is fast enough", "I surely can live without console", etc.
But when you install sbt, everything suddenly changes - you find that you can add a full jetty server to my project just by typing in a name and it's version, and do not need to search & download & place in appropriate dir & add to classpath & ... You just type that dependecy line, and forget about it. And you get many-many other goodies as well.
(by the way, I had the same thoughts before migrating to Scala from Java - "really, who cares about that boilderplate? I think slower than I type, don't I?" - and the same relevations when I made the move)
Now, I feel that sbt increased my speed at least twofold - clever recompilation and sbt-revolver plugin make development roll incredibly fast.
In process of testing this new tool, I wrote a small project, which I present to you today. It is not a project actually, a proof-of-concept - simple way to add remote management console to server application. I used JS as a language for console - mostly because it is already bundled with JRE in javax.script. Of course, it would be better to use Scala - but I do not have a decent understanding of Scala Presentation Compiler yet, and it would involve some hand-coding with reflection to access JVM objects.
For transport between user and client I chose Apache MINA, again, a bit of overkill - but I do not know about good alternatives, and MINA has a good API and is quick to set up.
So, long story short - our server listens on a port (12345), and clients connect by using simple telnet and use plain ol' JS to issue commands.
As they say - "twenty lines of code are worth a thousand words", so let's get to buisness.
object Mina {
def main(args:Array[String]) {
// configuring and starting the server
val acceptor = new NioSocketAcceptor
acceptor.setReuseAddress(true)
// adding default simple codec - it just passes strings between server and client
acceptor.getFilterChain()
.addLast("codec",
new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.defaultCharset(),
java.lang.System.getProperty("line.separator"),
java.lang.System.getProperty("line.separator"))))
acceptor.setHandler(new Handler())
acceptor.bind(new InetSocketAddress(12345))
}
// some methods which will be made available to remote console
def name = "afferono"
def push(s:String) = println("Got: " + s)
def shutdown = System.exit(0)
import scala.reflect.BeanProperty
@BeanProperty var ivar = 1
val arr = Array(1,2,3)
def getArr = "Array" + arr.toList.toString.drop(4)
}
class Handler extends IoHandlerAdapter {
import javax.script._
val engine = (new ScriptEngineManager).getEngineByName("JavaScript")
engine.put("mina",Mina)
override def sessionOpened(sess:IoSession) {
sess.write("Hello!")
}
override def messageReceived(sess:IoSession, message:java.lang.Object) {
message.asInstanceOf[String].trim match {
case ":help" =>
sess.write("Write JS, `enter` to submit. \":quit\" to end session. You have access to \"mina\" object.")
case ":quit" =>
sess.write("Goodbye :)")
sess.close(false);
case c =>
try {
val r = engine.eval(c)
if (r != null) sess.write(r)
} catch {
case e =>
sess.write("Got error! " + e.getClass.getName)
println(e.toString)
e.printStackTrace
}
}
}
}
And for conclusion - a small primer on Scala-JavaScript integration:
println("Hello!")
// prints hello to sbt's console
mina.ivar()
1
mina.ivar_$eq(2) // :(
mina.ivar()
2
mina.setIvar(3) // you can use JavaBean-style getters-setters, if you annotate var properly
mina.getIvar()
3
mina.arr()
[I@7c5b0668
mina.arr()[0]
1
mina.arr()[0] = 10
mina.arr()[0]
10
mina.push("tree") // normal method call
mina.shutdown()
Connection closed by foreign host.
// prints hello to sbt's console
mina.ivar()
1
mina.ivar_$eq(2) // :(
mina.ivar()
2
mina.setIvar(3) // you can use JavaBean-style getters-setters, if you annotate var properly
mina.getIvar()
3
mina.arr()
[I@7c5b0668
mina.arr()[0]
1
mina.arr()[0] = 10
mina.arr()[0]
10
mina.push("tree") // normal method call
mina.shutdown()
Connection closed by foreign host.
Comments
Post a Comment