11 August, 2010

Scalatra + Embedded Jetty

Lets face it; it is extraordinarily simple to deploy a quick & simple CGI script in languages like Perl or Python. It has often left me a very sad, sitting in Scala/Java land while admiring the beautifully simplistic and lightweight tools that my neighbours all have.

Sure, things like Lift & Grails make building big applications very very nice, but when you just need a simple form and touch of backend processing, I find that it is often harder than it needs to be. (Although if you have other suggestions I would love to be proven wrong here)

Came across a neat Scala library today called Scalatra that I believe fills this void. Essentially it is an abstraction over plain old java servlets to make REST style path parsing, form processing and response generation very very easy. It is based on a ruby library called Sinatra.

Take a look at this simple code that takes in two files and diffs them, outputting the diff output in a nice green (addition) and red+strikeout (removes).

package com.benkolera.scalatradiff

import org.scalatra._
import org.scalatra.fileupload._
import org.apache.commons.fileupload.FileItem
import ScalaDiff._

class DiffServlet extends ScalatraServlet with FileUploadSupport {

before {
contentType = "text/html"
}

get("/") {
<form method="post" enctype="multipart/form-data">
<p>Old File: <input type="file" name="oldFile" /></p>
<p>New File: <input type="file" name="newFile" /></p>
<input type="submit" />
</form>
}

post("/") {
val oldFile = fileParams("oldFile").getString
val newFile = fileParams("newFile").getString

<div>
{
diff(oldFile,newFile).map{
case Insert(t) => mkDiffSpan(t=t,colour="green")
case Delete(t) => mkDiffSpan(t=t,colour="red",strike=true)
case Equal(t) => mkDiffSpan(t=t)
}
}
</div>
}

def mkDiffSpan(
t: String,
colour: String = "black",
strike: Boolean = false
) = {
val style = "white-space:pre;color:"+colour + (
if (strike) ";text-decoration: line-through" else ""
)
<span style={style}>{t}</span>
}
}

There isn't much to say about the code. It is all nice, simple and easy to follow. Scala's inbuilt XML syntax makes things even easier by removing your need to bugger around with Strings. It also adds the neat bonus of validating that your XTHML is well formed upon code compile.

Also take good notice of those beautiful named and default parameters being used in mkDiffSpan. Perhaps one of the most simple additions to Scala in 2.8, I find that they clean up my code quite a lot. What you see there is cleaner (to me, at least) than how I would do it in 2.7.7. IMHO, the 2.7.7 version isn't that bad; it is just that the named and defaults is even better.

This would be my 2.7.7 version:


<div>
{
diff(oldFile,newFile).map{
case Insert(t) => mkDiffSpan(t,Some("green"),false)
case Delete(t) => mkDiffSpan(t,Some("red"),true)
case Equal(t) => mkDiffSpan(t,None,false)
}
}
</div>
}

def mkDiffSpan(
t: String,
colour: Option[String],
strike: Boolean
) = {
val col = colour.getOrElse("black")

val style = "white-space:pre;color:"+col + (
if (strike) ";text-decoration: line-through" else ""
)
<span style={style}>{t}</span>
}


All the code does is presents a simple form asking for two files. Upon submitting these files it diffs them and produces something that looks like this:



To play around, I also embedded jetty inside the code so that the application is run as a standalone jar rather than a war that is deployed somewhere. While this is nice and easy, it is probably of little help to the kind of applications you'll make with Scalatra. After all, your app will usually end up on a server somewhere and needs to be daemonised in some way. You can install and use jsvc, but it's probably just as easy to install jetty so you don't really win anything there. Food for thought though; the jar may be a better alternative if you're just running the thing locally (using it for a mock REST API for testing, tailing local files, etc) and don't need to run it in a hands off fashion.

Here is the code. Incredibly simple stuff:

package com.benkolera.scalatradiff

import org.mortbay.jetty.Server
import org.mortbay.jetty.servlet.{Context,ServletHolder}

object DiffApp {

def main(args: Array[String]) = {
val port =
args match {
case Array(port) => Some(port.toInt)
case _ => None
}

val server = new Server(port.getOrElse(8080))
val root = new Context(server,"/",Context.SESSIONS)
root.addServlet(new ServletHolder(new DiffServlet()), "/*")
server.start()
}
}

You can checkout the full code here if you desire:

http://github.com/benkolera/scalatra-diff