How to access POST request data with Scalatra

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 15.8, “How to access POST request data with Scalatra.”

Problem

You want to write a Scalatra web service method to handle POST data, such as handling JSON data sent as a POST request.

Solution

To handle a POST request, write a post method in your Scalatra servlet, specifying the URI the method should listen at:

post("/saveJsonStock") {
    val jsonString = request.body
    // deserialize the JSON ...
}

As shown, access the data that’s passed to the POST request by calling the request.body method.

The Discussion shows an example of how to process JSON data received in a post method, and two clients you can use to test a post method: a Scala client, and a command-line client that uses the Unix curl command.

Discussion

Recipe 15.3 shows how to convert a JSON string into a Scala object using the Lift-JSON library, in a process known as deserialization. In a Scalatra post method, you access a JSON string that has been POSTed to your method by calling request.body. Once you have that string, deserialize it using the approach shown in Recipe 15.3.

For instance, the post method in the following StockServlet shows how to convert the JSON string it receives as a POST request and deserialize it into a Stock object. The comments in the code explain each step:

package com.alvinalexander.app

import org.scalatra._
import scalate.ScalateSupport
import net.liftweb.json._

class StockServlet extends MyScalatraWebAppStack {

    /**
     * Expects an incoming JSON string like this:
     * {"symbol":"GOOG","price":"600.00"}
     */
    post("/saveJsonStock") {

        // get the POST request data
        val jsonString = request.body

        // needed for Lift-JSON
        implicit val formats = DefaultFormats

        // convert the JSON string to a JValue object
        val jValue = parse(jsonString)

        // deserialize the string into a Stock object
        val stock = jValue.extract[Stock]

        // for debugging
        println(stock)

        // you can send information back to the client
        // in the response header
        response.addHeader("ACK", "GOT IT")
    }

}

// a simple Stock class
class Stock (var symbol: String, var price: Double) {
    override def toString = symbol + ", " + price
}

The last step to get this working is to add the Lift-JSON dependency to your project. Assuming that you created your project as an SBT project as shown in Recipe 15.1, add this dependency to the libraryDependencies declared in the project/build.scala file in your project:

"net.liftweb" %% "lift-json" % "2.5+"

Test the POST method with Scala code

As shown in the code comments, the post method expects a JSON string with this form:

{"symbol":"GOOG","price":600.00}

You can test your post method in a variety of ways, including (a) a Scala POST client or (b) a simple shell script. The following PostTester object shows how to test the post method with a Scala client:

import net.liftweb.json._
import net.liftweb.json.Serialization.write
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.DefaultHttpClient

object PostTester extends App {

    // create a Stock and convert it to a JSON string
    val stock = new Stock("AAPL", 500.00)
    implicit val formats = DefaultFormats
    val stockAsJsonString = write(stock)

    // add the JSON string as a StringEntity to a POST request
    val post = new HttpPost("http://localhost:8080/stocks/saveJsonStock")
    post.setHeader("Content-type", "application/json")
    post.setEntity(new StringEntity(stockAsJsonString))

    // send the POST request
    val response = (new DefaultHttpClient).execute(post)

    // print the response
    println("--- HEADERS ---")
    response.getAllHeaders.foreach(arg => println(arg))

}

class Stock (var symbol: String, var price: Double)

The code starts by creating a Stock object and converting the object to a JSON string using Lift-JSON. It then uses the methods of the Apache HttpClient library to send the JSON string as a POST request: it creates an HttpPost object, sets the header content type, then wraps the JSON string as a StringEntity object before sending the POST request and waiting for the response.

When this test object is run against the Scalatra saveJsonStock method, it results in the following output:

--- HEADERS ---
ACK: GOT IT
Content-Type: text/html;charset=UTF-8
Content-Length: 0
Server: Jetty(8.1.8.v20121106)

Note that it receives the ACK message that was returned by the Scalatra post method. This isn’t required, but it gives the client a way to confirm that the data was properly received and processed by the server method (or that it failed).

Test the POST method with a curl command

Another way to test the post method is with a Unix shell script. The following curl command sets the Content-type header, and sends a sample JSON string to the Scalatra StockServlet post method as a POST request:

curl \
    --header "Content-type: application/json" \
    --request POST \
    --data '{"symbol":"GOOG", "price":600.00}' \
    http://localhost:8080/stocks/saveJsonStock

On Unix systems, save this command to a file named postJson.sh, and then make it executable:

$ chmod +x postJson.sh

Then run it to test your Scalatra web service:

$ ./postJson.sh

You won’t see any output from this command, but you should see the correct debugging output printed by the StockServlet in its output window. Assuming that you’re running your Scalatra web service using SBT, the debug output will appear there.

Notes

Recent versions of Scalatra use the Json4s library to deserialize JSON. This library is currently based on Lift-JSON, so the deserialization code will be similar, if not exactly the same. Either library will have to be added as a dependency.

The other important parts about this recipe are:

  • Knowing to use the post method to handle a POST request
  • Using request.body to get the POST data
  • Using response.addHeader("ACK", "GOT IT") to return a success or failure message to the client (though this is optional)
  • Having POST request client programs you can use

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations: