Creating a servlet with Scala

I’m learning Scala at the moment. It is a wicked and very powerful language. I can recommend anyone who knows Java and is tiered of the huge amount of code that is needed to write even the most mundane functionality to give it a try. Anyway, on my quest to learn Scala I am using the book Scala in Action, which I can highly recommend. In chapter 2 a Java servlet is required. The book shows the code that is required for the servlet, but does not explain how to create a servlet. Not very complicated of course, but why create the servlet using Java when you can create a servlet using Scala? What a great exercise 🙂 So I gave it a try. This is the result. Enjoy.

This post uses SBT. For a quick tutorial on SBT see my post Quick start tutorial to Simple Build Tool (SBT).

The code can be found in my GitHub repository.

Prerequisists

You have the following software installed:

  • Scala
  • SBT

Optionally:

  • Eclipse

Tutorial

Setup the project:

mkdir -p ~/projects/sbt/RestService
cd ~/projects/sbt/RestService
mkdir -p src/main/scala
mkdir -p src/main/webapp/WEB-INF

Initialize the project using SBT:

Note that lines prefixed with ‘>’ are commands that must be entered in the SBT editor in interactive mode.

sbt
> set name := "RestService"
> session save
> exit

Create a plugins.sbt file under the project/directory and add the sbteclipse plugin and Jetty plugin:

Notes:

  1. You should double check the version number to make sure you are using the latest version of the sbteclipse plugin and Jetty plugin.
  2. The extra empty lines between statements are intentional. The .sbt files are not Scala programs; they are a list of Scala expressions, where a blank line is the delimiter between these expressions.
cat > project/plugins.sbt << EOF
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0")

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.0.0-M7")
EOF

Update the build.sbt file with the following command:

Note that you should double check the Scala version you are running using the scala -version command.

cat >> build.sbt << EOF
organization := "my organisation"

version := "1.0.0-SNAPSHOT"

scalaVersion := "2.11.2"

jetty()

libraryDependencies += "javax.servlet" % "servlet-api" % "2.5" % "provided"

libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.0.2"

libraryDependencies += "commons-httpclient" % "commons-httpclient" % "3.1"
EOF

The last three statements add javax.servlet, scala-xml and commons-httpclient retrieve the libraries and their dependenciers and add them to the container classpath. Scala-XML has been factored out into a separate library as of Scala 2.11, so it is not included in Scala projects by default. So the dependency needs to be explicitly include in the project build.sbt.

Add a servlet:

cat > src/main/scala/servlets.scala << EOF
package restservice

import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.util.{ HashMap => JHashMap, Map => JMap }
import scala.collection.JavaConversions._
import scala.xml.NodeSeq
import scala.xml.Elem
import scala.xml.PrettyPrinter

class TestRestService extends HttpServlet {

  override def doGet(request: HttpServletRequest, response: HttpServletResponse) {
    response.setContentType("text/html")
   response.setCharacterEncoding("UTF-8")

   prettyPrintResponse(request, response, "doGet")
 }

  override def doPost(request: HttpServletRequest, response: HttpServletResponse) {
    response.setContentType("text/html")
    response.setCharacterEncoding("UTF-8")

    prettyPrintResponse(request, response, "doPost")
  }

  override def doDelete(request: HttpServletRequest, response: HttpServletResponse) {
    response.setContentType("text/html")
    response.setCharacterEncoding("UTF-8")

    prettyPrintResponse(request, response, "doDelete")
  }

  def prettyPrintResponse(request: HttpServletRequest, response: HttpServletResponse, callType: String) {
    val responseBody: NodeSeq =
      <html>
        <title>{ callType }</title>
        <body>
          <h1>{ callType } method called</h1>
            parameters:<br/>{ parameters(request) }<br/><br/>
            headers:<br/>{ headers(request) }
          </body>
        </html>

    val printer = new PrettyPrinter(80, 2)

    response.getWriter.write(printer.formatNodes(responseBody))
  }

  def makeValues(values: Array[String]): String = {
    var s = ""
    for (v: String <- values) if (s.length() == 0) s += s"$v" else s += s" $v"
    s
  }

  def parameters(request: HttpServletRequest): Elem = {
    val result =
        {
          for ((k: String, v: Array[String]) <- request.getParameterMap()) yield 
            <pre>{k} = {makeValues(v)}</pre>
        }
    result
  }

  def headers(request: HttpServletRequest): Elem = {
    val result =
        {
          for ((k: String) <- request.getHeaderNames.asInstanceOf[java.util.Enumeration[String]]) yield 
            <pre> {k} = {request.getHeader(k)}</pre>
        }
    result
  }

}
EOF

Add the WEB.XML:

cat > src/main/webapp/WEB-INF/web.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">

  <servlet>
    <servlet-name>TestRestServlet</servlet-name>
    <servlet-class>restservice.TestRestServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>TestRestServlet</servlet-name>
    <url-pattern>/test</url-pattern>
   </servlet-mapping>
</web-app>
EOF

To create the Eclipse project execute the following command from the root directory:

sbt eclipse

Start (or restart) the container with:

sbt
> container:start

Check the result in your browser:

http://localhost:8080/test?param_1=1&param_2=2

Alternatively you can use a plugin like the Firefox add-on RESTClient, which allows you to do al types of HTTP requests and set HTTP headers.

Stop the container with (first hit enter):

> container:stop

Change the default port Jetty is running on? Just change the Jetty() line in build.sbt to:

jetty(9090)

Build a .war file with:

sbt package

Leave a comment