Monday, June 14, 2010

Using Jetty and Jersey with Scala

Lately I've been playing around with Scala again and I wanted to build a small RESTful web app using Jersey running in Jetty and I couldn't find any working examples online.

First you need to download and install Scala and SBT, follow the instructions for SBT on the site. Then you need to create the SBT project (bold stuff is what i typed in);
$ sbt
Project does not exist, create new project? (y/N/s) y
Name: RestWebApp
Organization: none
Version [1.0]: 
Scala version [2.7.7]: 2.8.0.RC5
sbt version [0.7.4]: 
Getting Scala 2.7.7 ...
:: retrieving :: org.scala-tools.sbt#boot-scala
 confs: [default]
 2 artifacts copied, 0 already retrieved (9911kB/288ms)
Getting org.scala-tools.sbt sbt_2.7.7 0.7.4 ...
:: retrieving :: org.scala-tools.sbt#boot-app
 confs: [default]
 15 artifacts copied, 0 already retrieved (4096kB/149ms)
[success] Successfully initialized directory structure.
Getting Scala 2.8.0.RC5 ...
downloading ...
 [SUCCESSFUL ] org.scala-lang#scala-compiler;2.8.0.RC5!scala-compiler.jar (92356ms)
downloading ...
 [SUCCESSFUL ] org.scala-lang#scala-library;2.8.0.RC5!scala-library.jar (61762ms)
:: retrieving :: org.scala-tools.sbt#boot-scala
 confs: [default]
 2 artifacts copied, 0 already retrieved (14497kB/49ms)
[info] Building project RestWebApp 1.0 against Scala 2.8.0.RC5
[info]    using sbt.DefaultProject with sbt 0.7.4 and Scala 2.7.7

Now, you need to set up the dependencies for jersey and jetty, in the file project/build/RestWebApp.scala like this;
import sbt._

class RestWebAppProject(info: ProjectInfo) extends DefaultProject(info)
  val jersey = "com.sun.jersey" % "jersey-server" % "1.2"
  val jerseyJson = "com.sun.jersey" % "jersey-json" % "1.2"
  val jetty = "org.eclipse.jetty" % "jetty-server" % "8.0.0.M0"
  val jettyServlet = "org.eclipse.jetty" % "jetty-servlet" % "8.0.0.M0"

  val javanetDeps = "javanetDeps" at ""

Now, in your sbt console;

> reload
[info] Recompiling project definition...
[info] Source analysis: 1 new/modified, 0 indirectly invalidated, 0 removed.
[info] Building project RestWebApp 1.0 against Scala 2.8.0.RC5
[info] using RestWebAppProject with sbt 0.7.4 and Scala 2.7.7
> update
[info] == update ==
[info] :: retrieving :: none#restwebapp_2.8.0.RC5 [sync]
[info] confs: [compile, runtime, test, provided, system, optional, sources, javadoc]
[info] 20 artifacts copied, 0 already retrieved (3745kB/97ms)
[info] == update ==
[success] Successful.
[info] Total time: 1 s, completed Jun 14, 2010 8:31:40 PM

Now you are good to go, create a file in src/main/scala/something.scala:
package marcus.test

import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.servlet.ServletException
import org.eclipse.jetty.server.{Server, Request}
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.nio.SelectChannelConnector
import org.eclipse.jetty.servlet.{ServletHolder, ServletContextHandler}
import com.sun.jersey.spi.container.servlet.ServletContainer

import{GET, Produces, Path}

object WebRunner {
  def main(args: Array[String]) {

    val server = new Server(8080)
    val connector = new SelectChannelConnector()
    val holder:ServletHolder = new ServletHolder(classOf[ServletContainer])
    val context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS)
    context.addServlet(holder, "/*")

class TestResource {
  def hello() = {

and in your sbt console:

> compile
> run

and visit http://localhost:8080/helloworld and you should see "HELLO!!". Note that in a real project you might want to separate the classes in different files etc.