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.

Comments

Popular posts from this blog

How to create your own simple 3D render engine in pure Java

Solving quadruple dependency injection problem in Angular

Better CLI option parsing in Scala