portable perspectives

a blog about programming, life, universe and everything

Step, a scala web picoframework

So, after playing with scala on Google App Engine two days ago, I spent a bit of time yesterday (while waiting for food from girlfriend) and today (while waiting plane to go eat at my parents' easter breakfast, unrelated) writing my hack for a web framework.

I mostly wanted to try scala's cool abstraction features, so I thought I could try to produce something that had more or less the feeling of immediateness of sinatra.

To get ruby-like blocks in scala you only need to define functions with by-name arguments in this way:

1
2
3
def foo(x:String)(fun: =>Any)

foo("something") {block returning something to be evaluated later }

Of course, what was needed at this point was something that allowed me to match the url and the http method. This is the code to allow that, using a simple Map from path+method to anonymous functions

1
2
3
4
5
6
7
8
9
10
11
12

abstract class Step extends HttpServlet {
  val Map = new HashMap[(String,String),(Any=>String)]() 
  override def service(request:HttpServletRequest ,response: HttpServletResponse) {
        val method=request.getMethod
        val path=request.getRequestURI
        response.getWriter.println(Map(method,path)());
    }
  
  def get(path:String)(fun: =>Any) =    Map.put(("GET",path),x=>fun.toString)
  def post(path:String)(fun: =>Any) =    Map.put(("POST",path),x=>fun.toString)  
}

I believe the types could be simplified, as I'd just need a ()=>Any function not Any=>String, but I could not come up with the proper definition yet :)

Anyway, this can be used in a pretty simple way, and thanks to Scala's xml literal you can have code like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ScalaHello extends Step {
  get("/") {
    <form action="/post" method="POST">
      <textarea name="key"/>
      <input type="submit"/>
    </form>
  }

  post("/post") {
    "received a post"
  }

  def hello() = "hello scala world!"
  
  get("/hello) {
    hello
  }

  get("/number") { 1 }
  
}

Now, obviously we have the issue of passing arguments. The by-name trick does not allow us to pass parameters and we cannot inject a new variable into the closure scope (I believe), nor evaluate the block in a different scope a-la ruby's instance_eval.

It is possible in scala to define implicit variables that have dynamic scope, but I could not find example on how that would be possible with byname blocks.

What we can do though, is define a method params to access such variable.

Yet, we are defining all the "get"s inside the same object, so even if we can access the method to access the parameters we just shifted the problem of having that shared object, and what if different threads access it at the same time?

Yet, scala already provides what we need, namely dynamic scope, in the form of the DynamicVariable class, which can be used like

1
2
3
4
  private val dynamic = new DynamicVariable[SomeType](init)
   dynamic.withValue(aValue) {
     block where dynamic contains aValue
   }

Having defined the params method properly we have this code working nicely

1
2
3
4
5
6
7
8
9
10
class ScalaHello extends Step {
  get("/") {
    <form action="/post" method="POST">
      <textarea name="key"/>
      <input type="submit"/>
    </form>
  }
  post("/post") {
    "hello, this is your input "+params("key")
  }

I did not implement the pattern matching in the URL as sinatra does, but that should also be simple.

The code is pretty ugly and having started really reading about scala only few days ago it can be probably improved in many ways. The first things that come to my mind

  • I am shuffling around parameters to keep the function closure, but probably cleaning up the types should avoid this
  • I am using java.util.Map and java arrays, which means even if the code is well typed it can still go wrong
  • I have a cast that could probably be avoided
  • there is no error handling
  • the code still needs to me mapped via the evil web.xml to the / path. It should be possible to avoid this, but I have no clue on how to do it :)

Anyway, as the code is 30 lines of which about 9 are "real" code, I am fairly impressed :)

The full code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package step
import javax.servlet._;
import javax.servlet.http._;
import scala.util.DynamicVariable
import scala.collection.mutable.HashMap

abstract class Step extends HttpServlet {
  type Params = java.util.Map[String,Array[String]]
  val Map = new HashMap[(String,String),(Any=>String)]()
  val paramsMap = new DynamicVariable[Params](null)

  def params(name:String):String = paramsMap.value.get(name)(0)
  override def service(request:HttpServletRequest ,response: HttpServletResponse) {
      try {
        response.setContentType("text/html")
        paramsMap.withValue(request.getParameterMap.asInstanceOf[Params]) {
          response.getWriter.println(Map(request.getMethod,request.getRequestURI)());
        }
      }
       catch {
         case ex:NoSuchElementException => response.getWriter.println("requesting "+request.getMethod+" "+ request.getRequestURI+" but only have "
                                                   +Map)
       }
    }  
  def get(path:String)(fun: =>Any) =    Map.put(("GET",path),x=>fun.toString)
  def post(path:String)(fun: =>Any) =    Map.put(("POST",path),x=>fun.toString)
}

Please post suggestions if you want, but remeber that for heavy lifting it would still better to use a lift ;)

PS this code does not work on google app engine as of now, but I am getting this message

Error: Server Error
The server encountered an error and could not complete your request.

If the problem persists, please report your problem and mention this error message and the query that caused it.

which may mean this is a bug on their side, probably related to some Thread behaviour as I believe DynamicVariable is defined in terms of ThreadLocal. I'll let you know if I sort it out. UPDATE actually, it seems related to scala.collections.mutable.HashMap not being loaded, even thought the jar is in lib as the others. Mh..

UPDATE 2 well, it seems I had just a misconfigured path, everything works like a charm on Google App Engine too, yay!

See all comments

Using Scala with Google App Engine

I just got access yesterday to the new Google App Engine for Java SDK, and I thought it would be fun to check if Scala works on it.

Well, it does.

Fow what is worth, I followed the standard procedure outlined in the google documentation and I was able to run my small servlet from eclipse, in java.

Simple steps to get your scala app on google app engine

  • download the GAE plugin for ecplise
  • download, if necessary, the scala plugin
  • create a new Google Web app project
  • add "scala nature" to the project
  • define a new Scala class as an ancestor to the existing servlet, letting the former implement the servlet behaviour

After this the basic servlet that was managed as the main run configuration from eclipse still worked correctly, meaning I could run it, stop it and deploy everything correctly. Adding a few other Scala classes still let the thing work nicely.

It was obviousl possible at this point to delete the old useless java class and just keep the scala servlet. A pure scala app is now able to run on google app engine.

After that, I'd have loved to build a tiny online REPL (at least I know that nothing bad will happen as google took care of the sandboxing) but while I was trying to use the existing scala.tools.nsc.Interpreter class I hit a problem causes by that same sandbox: it seems that the Scala code accesses the file system (mostly calls to File.isDirectory and similar), and those are caught by the SecurityManager who raises an AccessControlException. Eh, just what I asked for :)

Probably it is possible to work around this issue somehow, but it seems It would require more knowledge than that I currently have, so I'm leaving it until I have more time and experience.

For those not interested in using eclipse a nice writeup of the steps necessary to use Scala with GAE are here, it is again pretty simple, and it is probably a better approach.

See all comments

AddThis Social Bookmark Button