Up: Part II

15 RESTful Web Services

Many web applications today offer an API [P]  [P] Application Programming Interface that allows others to extend the functionality of the application. An API is a set of exposed functions that is meant to allow third parties to reuse elements of the application. There is a number of sites that catalog the available APIs, such as ProgrammableWeb (see http://www.programmableweb.com/). An example of a site that has combined the GoogleMaps and Flickr APIs is FlickrVision.com [Q]  [Q] http://flickrvision.com/. FlickrVision allows users to visualize where in the world recent photos have been taken by combining the geolocation information embedded in the photos and the mapping system of GoogleMaps. This is just one example of an API mashup, and there are countless other examples.

15.1 Some Background on REST

Before we dive into the details of building a RESTful API with Lift, let’s start by discussing a little about REST and the protocol that it sits atop: HTTP. If you’re already familiar with REST and HTTP, feel free to skip to the implementation in Section 15.2↓.

15.1.1 A Little Bit about HTTP

As we build our web service, it will to be helpful to know a few things about HTTP [R]  [R] Hypertext Transfer Protocol requests and responses. If you’re comfortable with the Request-Response cycle then feel free to jump to Section 15.1.2↓ to get down to business.
A simplification of how the web works is that clients, typically web browsers, send HTTP Requests to servers, which respond with HTTP Responses. Let’s take a look at an exchange between a client and a server.
We’re going to send a GET request to the URI http://demo.liftweb.net/ using the cURL utility. We’ll enable dumping the HTTP protocol header information so that you can see all of the information associated with the request and response. The cURL utility sends the output shown in Listing 15.1.1↓:
cURL Request
$ curl -v http://demo.liftweb.net/ 
* About to connect() to demo.liftweb.net port 80 (#0) 
*   Trying 64.27.11.183... connected 
* Connected to demo.liftweb.net (64.27.11.183) port 80 (#0) 
> GET / HTTP/1.1 
> User-Agent: curl/7.19.0 (i386-apple-darwin9.5.0) libcurl/7.19.0 zlib/1.2.3 
> Host: demo.liftweb.net 
> Accept: */*
And gets the corresponding response, shown in Listing 15.1.1↓, from the server:
cURL Response
< HTTP/1.1 200 OK 
< Server: nginx/0.6.32 
< Date: Tue, 24 Mar 2009 20:52:55 GMT 
< Content-Type: text/html 
< Connection: keep-alive 
< Expires: Mon, 26 Jul 1997 05:00:00 GMT 
< Set-Cookie: JSESSIONID=5zrn24obipm5;Path=/ 
< Content-Length: 8431 
< Cache-Control: no-cache; private; no-store; 
  must-revalidate; max-stale=0; post-check=0; pre-check=0; max-age=0 
< Pragma: no-cache 
< X-Lift-Version: 0.11-SNAPSHOT 
<  
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:lift="http://liftweb.net" xmlns="http://www.w3.org/1999/xhtml">
<head>....
This seems pretty straightforward: we ask for a resource, and the server returns it to us. Take a look at the HTTP request. We’d like to point out the method called, in this case a “GET”, and the URI, which is “http://demo.liftweb.net/”. Method calls and addresses are what make the web work. You can think of the web as a series of method calls on varying resources, where the URI (Uniform Resource Identifier) identifies the resource upon which the method will be called.
Methods are defined as part of the HTTP standard, and we’ll use them in our API. In addition to GET, the other HTTP methods are POST, DELETE, PUT, HEAD, and OPTIONS. You may also see methods referred to as actions or verbs. In this chapter, we will focus on using GET and PUT for our API.
As do Requests, Responses come with a few important pieces of information. Of note are the Response Code and the Entity Body. In the above example, the Response Code is “200 OK” and the Entity Body is the HTML content of the webpage, which is shown as the last two lines starting with “<!DOCTYPE.” We’ve truncated the HTML content here to save space.
This was a quick overview of HTTP, but if you’d like to learn more, take a look at the protocol definition found at [S]  [S] http://www.ietf.org/rfc/rfc2616.txt. We wanted to point out a few of the interesting parts of the cycle before we got into building a REST API.

15.1.2 Defining REST

Roy Fielding defined REST in his dissertation [T]  [T] http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm and defined the main tenet of the architecture to be a uniform interface to resources. “Resources” refers to pieces of information that are named and have representations. Examples include an image, a Twitter status, or a timely item such as a stock quote or the current temperature. The uniform interface is supported by a set of constraints that include the following:
These features are shared by both the web and by RESTful services. REST adds additional constraints regarding interacting with resources:
Fielding’s goal was to define a method that allowed machine-to-machine communication to mimic that of browser-to-server communication and to take advantage of HTTP as the underlying protocol.

15.1.3 Comparing XML-RPC to REST Architectures

What, then, is the difference between a RESTful architecture and a traditional RPC [U]  [U] Remote Procedure Call architecture?
An RPC application follows a more traditional software development pattern. It ignores most of the features offered by HTTP, such as the HTTP methods. Instead, the scoping and data to be used by the call are contained in the body of a POST request. XML-RPC works similarly to the web for getting resources, but breaks from the HTTP model for everything else by overloading the POST request. You will often see the term SOAP when referring to an XML-RPC setup, because SOAP permits the developer to define the action and the resource in the body of the request and ignore the HTTP methods.
RESTful architectures embrace HTTP. We’re using the web; we may as well take advantage of it.

15.2 A Simple API for PocketChange

We’re going to start with a simple example, so we’ll only touch on some of the more complex steps of building a web service, such as authentication and authorization. If you would like to see the code involved in performing authentication and authorization for our REST API, see Section 9.9↑. For the purposes of this example, we’re going to model two calls to the server: a GET request that responds with the details of an expense, and a PUT to add a new expense.The URLs will be:
Note that a URL (Uniform Resource Locator) is a type of URI in which the URI also serves to locate the resource on the web. A URN (Uniform Resource Name) is another type of URI that provides a unique name to a resource without specifying an actual location, though it may look a lot like a URL. For more information on the distinctions among URIs, see http://en.wikipedia.org/wiki/Uniform_Resource_Name.
We would like the REST API to support both XML and JSON for this data. Additionally, we would like to support an Atom feed on an account so that people can track expenses as they’re added. The URL for the Atom feed will be a GET of the form:
http://www.pocketchangeapp.com/api/account/<account id>
In the next few sections we’ll show how you can easily add support for these methods and formats using Lift.

15.3 Adding REST Helper Methods to our Entities

In order to simplify our REST handler code, we would like to add some helper methods for our Expense entity to support generation of both XML and JSON for our consumers. We’ll add these to a new RestFormatters object inside the src/main/scala/com/pocketchangeapp/RestFormatters.scala source file. First, we add some common functionality in Listing 15.3↓ by adding several helper methods for computing REST header values.
Common Expense REST Helpers
  /* The REST timestamp format. Not threadsafe, so we create
   * a new one each time. */
  def timestamp = new SimpleDateFormat("yyyy-MM-dd’T’HH:mm:ss’Z’")
​
  // A simple helper to generate the REST ID of an Expense
  def restId (e : Expense) =
    "http://www.pocketchangeapp.com/api/expense/" + e.id
​
  // A simple helper to generate the REST timestamp of an Expense
  def restTimestamp (e : Expense) : String =
    timestamp.format(e.dateOf.is)
Listing 15.3↓ shows a helper method for generating a proper JSON representation of a given Expense using the Lift JSON DSL. Although Expense is a Mapper entity, we don’t use the Expense.asJs method inherited from Mapper because we want to better control the format.
Expense Entity JSON Formatters
/**
 * Generates the JSON REST representation of an Expense
 */
def toJSON (e : Expense) : JValue = {
  import net.liftweb.json.JsonDSL._
  import net.liftweb.json.JsonAST._
​
  ("expense" ->
    ("id" -> restId(e)) ~
    ("date" -> restTimestamp(e)) ~
    ("description" -> e.description.is) ~
    ("accountname" -> e.accountName) ~
    ("accountid" -> e.account.obj.open_!.id.is) ~
    ("amount" -> e.amount.is.toString) ~
    ("tags" -> e.tags.map(_.name.is).mkString(",")))
}
Finally, Listing 15.3↓ shows the toXML method, which will generate properly formatted XML for a given Expense. Like toJSON, we don’t use the Expense.toXml method because we want more control over the generated format. Instead, we simply convert the result of toJSON into XML using the net.liftweb.json.Xml helper object.
Expense Entity XML REST Formatter
import net.liftweb.json.Xml
/**
 * Generates the XML REST representation of an Expense
 */
def toXML (e : Expense) : Node = Xml.toXml(toJSON(e)).first

15.4 Multiple Approaches to REST Handling

As Lift has evolved, two main approaches have emerged that allow you to perform RESTful operations. In Lift 1.0 and up, you can add custom dispatch (Section 3.8 on page 1↑) on your API URLs to call custom handlers for your REST data. In Lift 2.0, the new net.liftweb.http.rest.RestHelper was introduced that vastly simplifies not only the dispatch for given operations, but also assists with conversion of requests and responses to both XML and JSON. Because custom dispatch is still very much a first-class feature of Lift we will cover both approaches here.
Before we get into the details of each method, there are two last helpers we’d like to define. Listing 15.4↓ shows an unapply method that we add to our Expense MetaMapper so that we can use Expense as an extractor in pattern matching. In this code we not only attempt to match by using a provided String as the Expense’s primary key, but we also compute whether the Expense is in a public account. This assists us in determining authorization for viewing a given Expense.
Adding an Extractor for Expense
import net.liftweb.util.ControlHelpers.tryo
/**
 * Define an extractor that can be used to locate an Expense based
 * on its ID. Returns a tuple of the Expense and whether the
 * Expense’s account is public.
 */
def unapply (id : String) : Option[(Expense,Boolean)] = tryo {
  find(By(Expense.id, id.toLong)).map { expense =>
     (expense,
      expense.account.obj.open_!.is_public.is)
  }.toOption
} openOr None
Similarly, Listing 15.4↓ shows an extractor on the Account MetaMapper that matches an Account based on its primary key.
Adding an Extractor for Account
import net.liftweb.util.Helpers.tryo
/**
 * Define an extractor that can be used to locate an Account based
 * on its ID.
 */
def unapply (id : String) : Option[Account] = tryo {
  find(By(Account.id, id.toLong)).toOption
} openOr None

15.4.1 Using Custom Dispatch

Now that we’ve discussed our design, let’s see the code that will handle the routing. In the package com.pocketchangeapp.api, we have an object named DispatchRestAPI, which we’ve defined in src/main/scala/com/pocketchangeapp/api/RestAPI.scala. In DispatchRestAPI, we define a custom dispatch function to pattern match on the request and delegate to a handler method. The custom dispatch function is shown in Listing 15.4.1↓. You can see that we use our extractors in the matching for both Expenses and Accounts. We’ll cover the processing of PUTs in Section 15.5↓, and the Atom processing in Section 15.7↓.
REST Method Routing
// Import our methods for converting things around
import RestFormatters._
​
def dispatch: LiftRules.DispatchPF = {
  // Define our getters first
  case Req(List("api", "expense", Expense(expense,_)), _, GetRequest) =>
     () => nodeSeqToResponse(toXML(expense)) // default to XML
  case Req(List("api", "expense", Expense(expense,_), "xml"), _, GetRequest) =>
     () => nodeSeqToResponse(toXML(expense))
  case Req(List("api", "expense", Expense(expense,_), "json"), _, GetRequest) =>
     () => JsonResponse(toJSON(expense))
  case Req(List("api", "account", Account(account)), _, GetRequest) =>
     () => AtomResponse(toAtom(account))
​
  // Define the PUT handler for both XML and JSON MIME types
  case request @ Req(List("api", "account", Account(account)), _, PutRequest)
    if request.xml_? =>
      () => addExpense(fromXML(request.xml,account),
                       account,
                       result => CreatedResponse(toXML(result), "text/xml"))
  case request @ Req(List("api", "account", Account(account)), _, PutRequest)
    if request.json_? =>
      () => addExpense(fromJSON(request.body,account),
                       account,
                       result => JsonResponse(toJSONExp(result), Nil, Nil, 201))
​
  // Invalid API request - route to our error handler
  case Req("api" :: x :: Nil, "", _) =>
    () => BadResponse() // Everything else fails
}
Our DispatchRestAPI object mixes in the net.liftweb.http.rest.XMLApiHelper trait, which includes several implicit conversions to simplify writing our REST API. Remember that LiftRules.DispatchPF must return a function () ⇒ Box[LiftResponse] (Section 3.8 on page 1↑), so we’re using the implicit putResponseInBox as well as explicitly calling nodeSeqToResponse to convert our API return values into the proper format.
The server will now service GET requests with the appropriate formatter function and will handle PUT requests with the addExpense method (which we’ll define later in this chapter).
We hook our new dispatch function into LiftRules by adding the code shown in Listing 15.4.1↓ to our Boot.boot method.
Setting up REST Dispatch
LiftRules.dispatch.prepend(DispatchRestAPI.dispatch)

15.4.2 Using the RestHelper Trait

New in Lift 2.0 is the net.liftweb.http.rest.RestHelper trait. This trait simplifies the creation of REST APIs that support both XML and JSON. For our example, we’ll define the RestHelperAPI object in our RestAPI.scala source file.
Before we get into the details of actual processing with RestHelper, we want to point out some useful parts of its API. First, RestHelper provides a number of built-in extractors for matching not only what HTTP verb a given request uses, but also the format of the request (JSON or XML). These extractors are:
We’ll demonstrate in the following sections how to use these extractors. Note that you can add additional rules for the JsonReq and XmlReq extractors by overriding the
RestHelper.suplimentalJsonResponse_?
and suplimentalXmlResponse_? (yes, those are spelled incorrectly) methods to perform additional tests on the request. For example, Listing 15.4.2↓ shows how we can use the existence of a given header to determine whether a request is XML or JSON.
Using a Cookie to Determine the Request Type
override def suplimentalJsonResponse_? (in : Req) = 
  in.header("This-Is-A-JSON-Request").isDefined
override def suplimentalXmlResponse_? (in : Req) = 
  in.header("This-Is-A-XML-Request").isDefined
One important difference between RestHelper and our DispatchRestAPI examples that we want to point out is that RestHelper determines whether a request is XML or JSON based on the Accept header and/or the suffix of the path (e.g. /api/expense/1.xml), whereas our DispatchRestAPI used the last component of the path (/api/expense/1/xml). Either approach is valid, just be aware if you’re copying this example code.
Next, like the XMLApiHelper trait, RestHelper provides a number of implicit conversions to LiftResponse from a variety of inputs. We’re not going to cover these directly here, but we’ll point out where we use them in this section.
Similar to our DispatchRestAPI handler, we need to define a set of patterns that we can match against. Unlike DispatchRestAPI, however, RestHelper defines four PartialFunction methods where we can add our patterns: serve, serveJx, serveJxa and serveType. These functions provide increasing automation (and control) over what gets served when the request matches a pattern. We won’t be covering serveType here, since it’s essentially the generalized version that serve, serveJx and serveJxa use behind the scenes.

15.4.2.1 The serve Method

Let’s start with the serve method. This method essentially corresponds one-to-one with our DispatchRestAPI.dispatch method. Listing 15.4.2.1↓ shows how we could handle Atom requests, as well as requests that don’t specify a format, using RestHelper. Note our use of the RestHelper extractors to match the HTTP Verb being used. Also note that we’re using an implicit conversion from a Box[T] to a Box[LiftResponse] when an implicit function is in scope that can convert T into a LiftResponse. In our example, Full(toXML(expense)) is equivalent to boxToResp(Full(toXML(expense)))(nodeToResp). Finally, the serve method can be invoked multiple times and the PartialFunctions will be chained together.
Using RestHelper.serve
// Service Atom and requests that don’t request a specific format
serve {
  // Default to XML
  case Get(List("api", "expense", Expense(expense,_)), _) =>
    () => Full(toXML(expense))
  case Get(List("api", "account", Account(account)), _) =>
    () => Full(AtomResponse(toAtom(account)))
}
We use similar calls to hook our PUT handlers, shown in Listing 15.4.2.1↓.
Using serve to handle PUTs
// Hook our PUT handlers
import DispatchRestAPI.addExpense
serve {
  case XmlPut(List("api", "account", Account(account)), (body, request)) =>
    () => Full(addExpense(fromXML(Full(body),account),
                          account,
                          result => CreatedResponse(toXML(result), "text/xml")))
  case JsonPut(List("api", "account", Account(account)), (_, request))  =>
    () => Full(addExpense(fromJSON(request.body,account),
                          account,
                          result => JsonResponse(toJSON(result), Nil, Nil, 201)))
}

15.4.2.2 The serveJx Method

Like the serve method, serveJx performs pattern matching on the request. However, serveJx allows you to specify a conversion function that matches against the requested format
(net.liftweb.http.rest.JsonSelect or net.liftweb.http.rest.XmlSelect) and perform your conversion there. Then, all you need to do is match once against a given path and serveJx will utilize your conversion function to return the proper result. Listing 15.4.2.2↓ shows how we can use a new implicit conversion to handle our format-specific GETs. The single match in our serveJx call replaces two lines in our DispatchRestAPI.dispatch method.
Using RestHelper.serveJx
// Define an implicit conversion from an Expense to XML or JSON
import net.liftweb.http.rest.{JsonSelect,XmlSelect}
implicit def expenseToRestResponse : JxCvtPF[Expense] = {
  case (JsonSelect, e, _) => toJSON(e)
  case (XmlSelect, e, _) => toXML(e)
}
​
serveJx {
  case Get(List("api", "expense", Expense(expense,_)), _) => Full(expense)
}
In addition to providing your own conversion function, serveJx can utilize the RestHelper autoconversion functionality. To use this, simply use the auto method to wrap whatever you want to return. Listing 15.4.2.2↓ shows an example of returning a contrived data object with auto.
Using auto to Convert Return Values
// Just an example of autoconversion
serveJx {
  case Get(List("api", "greet", name),_) =>
     auto(Map("greeting" ->
              Map("who" -> name,
                  "what" -> ("Hello at " + new java.util.Date))))
}
The conversion is actually performed with the net.liftweb.json.Extraction object, so you can autoconvert anything that Extraction can handle. This includes:

15.4.2.3 The serveJxa Method

The serveJxa method is basically the same as the serve and serveJx methods, except that anything that is returned will be automatically converted to JSON via the
net.liftweb.json.Extraction.decompose method.

15.5 Processing Expense PUTs

Now that we’re handling the API calls, we’ll need to write the code to process and respond to requests. The first thing we need to do is deserialize the Expense from the either an XML or JSON request.
In PocketChange our use of BigDecimal values to represent currency amounts means that we can’t simply use the lift-json deserialization support (Section C.10 on page 1↓). While lift-json is very good and would make this much simpler, it parses decimal values as doubles which can lead to rounding and precision issues when working with decimal values. Instead, we will need to write our own conversion functions.
To simplify error handling, we break this processing up into two format-specific methods that convert to a Map representation of the data, and another method that converts the intermediate Map/List into an Expense. Listing 15.5↓ shows the fromXML method in the RestFormatters object. This method performs some basic validation to make sure we have the required parameters, but otherwise doesn’t validate the values of those parameters. Note that we provide the Account to fromXML so that we can resolve tag names in the fromMap method (which we’ll cover momentarily).
Deserializing XML to an Expense
def fromXML (rootNode : Box[Elem], account : Account) : Box[Expense] = 
 rootNode match {
  case Full(<expense>{parameters @ _*}</expense>) => {
    var data = Map[String,String]()
​
    for(parameter <- parameters) {
      parameter match {
        case <date>{date}</date> => data += "date" -> date.text
        case <description>{description}</description> =>
          data += "description" -> description.text
        case <amount>{amount}</amount> => data += "amount" -> amount.text
        case <tags>{ tags }</tags> => data += "tags" -> tags.text
        case _ => // Ignore (could be whitespace)
      }
    }
​
    fromMap(data, account)
  }
  case other => Failure("Missing root expense element")
}
Similarly, Listing 15.5↓ shows our fromJSON method.
Deserializing JSON to an Expense
def fromJSON (obj : Box[Array[Byte]], account : Account) : Box[Expense] =
 obj match {
  case Full(rawBytes) => {
    // We use the Scala util JSON parser here because we want to avoid parsing
    // numeric values into doubles. We’ll just leave them as Strings
    import scala.util.parsing.json.JSON
    JSON.perThreadNumberParser = { in : String => in }
​
    val contents = new String(rawBytes, "UTF-8")
    JSON.parseFull(contents) match {
      case Some(data : Map[String,Any]) => {
        fromMap(data.mapElements(_.toString), account)
      }
      case other => Failure("Invalid JSON submitted: \"%s\"".format(contents))
    }
  }
  case _ => Failure("Empty body submitted")
}
Finally, Listing 15.5↓ shows our fromMap method, which takes the data parsed by fromJSON and fromXML and converts it into an actual expense.
Converting the Intermediate Data to an Expense
def fromMap (data : scala.collection.Map[String,String],
             account : Account) : Box[Expense] = {
  val expense = Expense.create
​
  try {
    val fieldParsers : List[(String, String => Expense)] =
      ("date", (date : String) => expense.dateOf(timestamp.parse(date))) ::
      ("description", (desc : String) => expense.description(desc)) ::
      ("amount", (amount : String) => expense.amount(BigDecimal(amount))) :: Nil
​
    val missing = fieldParsers.flatMap {
      field => // We invert the flatMap here to only give us missing values
        if (data.get(field._1).map(field._2).isDefined) None else Some(field._1)
    }
 
    if (missing.isEmpty) {
      expense.account(account)
      data.get("tags").foreach {
        tags => expense.tags(tags.split(",").map(Tag.byName(account.id.is,_)).toList)
      }
      Full(expense)
    } else {
      Failure(missing.mkString("Invalid expense. Missing: ", ",", ""))
    }
  } catch {
    case pe : java.text.ParseException => Failure("Failed to parse date")
    case nfe : java.lang.NumberFormatException =>
      Failure("Failed to parse amount")
  }
}
Now that we’ve converted the PUT data into an Expense, we need to actually perform our logic and persist the submitted Expense. Listing 15.5↓ shows our addExpense method, which matches against the parsed Expense and either runs validation if the parse succeeded, or returns an error response to the user if something failed. If validation fails, the user is similarly notified. The success parameter is a function that can be used to generate the appropriate response based on the newly created Expense. This allows us to return the new Expense in the same format (JSON, XML) in which it was submitted (see the dispatch function, Listing 15.4.1↑).
Saving the submitted Expense
def addExpense(parsedExpense : Box[Expense],
               account : Account,
               success : Expense => LiftResponse): LiftResponse = 
 parsedExpense match {
  case Full(expense) => {
    val (entrySerial,entryBalance) =
      Expense.getLastExpenseData(account, expense.dateOf)
​
    expense.account(account).serialNumber(entrySerial + 1).
    currentBalance(entryBalance + expense.amount)
    expense.validate match {
      case Nil => {
        Expense.updateEntries(entrySerial + 1, expense.amount.is)
        expense.save
        account.balance(account.balance.is + expense.amount.is).save
​
        success(expense)
      }
      case errors => {
        val message = errors.mkString("Validation failed:", ",","")
        logger.error(message)
        ResponseWithReason(BadResponse(), message)
      }
    }
  }
  case Failure(msg, _, _) => {
    logger.error(msg)
    ResponseWithReason(BadResponse(), msg)
  }
  case error => {
    logger.error("Parsed expense as : " + error)
    BadResponse()
  }
}

15.6 The Request and Response Cycles for Our API

At the beginning of this chapter, we showed you a request and response conversation for
http://demo.liftweb.net/
. Let’s see what that looks like for a request to our API. Listing 15.6↓ shows an XML GET request for a given expense. Note that we’re not showing the HTTP Basic authentication setup, required by our authentication configuration (Section 9.9 on page 1↑).
Request and Response for XML GET
Request:
http://www.pocketchangeapp.com/api/expense/3 GET
​
Response:
<?xml version="1.0" encoding="UTF-8"?>
<expense>
  <id>http://www.pocketchangeapp.com/api/expense/3</id>
  <accountname>Test</accountname>
  <accountid>1</accountid>
  <date>2010-10-06T00:00:00Z</date>
  <description>Receipt test</description>
  <amount>12.00</amount>
  <tags>test,receipt</tags>
</expense>
Listing 15.6↓ shows the same request in JSON format.
Request and Response for JSON GET
Request:
http://www.pocketchangeapp.com/api/expense/3/json GET
​
Response:
{"id":"http://www.pocketchangeapp.com/api/expense/3",
 "date":"2010-10-06T00:00:00Z",
 "description":"Receipt test",
 "accountname":"Test",
 "accountid":1,
 "amount":"12.00",
 "tags":"test,receipt"}
Listing 15.6↓ shows the output for a PUT conversation:
Request and Response for an XML PUT
Request:
http://www.pocketchangeapp.com/api/account/1 - PUT - addEntry(request) + XML Body
​
Request Body:
<expense>
  <date>2010-07-05T14:22:00Z</date>
  <description>Test</description>
  <amount>12.41</amount>
  <tags>test,api</tags>
</expense>
​
Response:
<?xml version="1.0" encoding="UTF-8"?>
<expense>
  <id>http://www.pocketchangeapp.com/api/expense/10</id>
  <accountname>Test</accountname>
  <accountid>1</accountid>
  <date>2010-07-05T14:22:00Z</date>
  <description>Test</description>
  <amount>12.41</amount>
  <tags>api,test</tags>
</expense>   

15.7 Extending the API to Return Atom Feeds

In addition to being able to fetch specific expenses using our API, it would be nice to be able to provide a feed of expenses for an account as they’re added. For this example, we’ll add support for Atom [V]  [V] http://tools.ietf.org/html/rfc4287, a simple publishing standard for content syndication. The first thing we need to do is write a method to generate an Atom feed for a given Account. Although Atom is XML-based, it’s sufficiently different enough from our REST API XML format that we’ll just write new methods for it. Listing 15.7↓ shows the toAtom methods (one for Account, one for Expense) in our RestFormatters object that will handle the formatting.
The toAtom Methods
def toAtom (a : Account) : Elem = {
  val entries = Expense.getByAcct(a,Empty,Empty,Empty,MaxRows(10))
​
  <feed xmlns="http://www.w3.org/2005/Atom">
    <title>{a.name}</title>
    <id>urn:uuid:{a.id.is}</id>
    <updated>{entries.headOption.map(restTimestamp) getOrElse
              timestamp.format(new java.util.Date)}</updated>
    { entries.flatMap(toAtom) }
  </feed>
}
​
def toAtom (e : Expense) : Elem =
  <entry>
    <id>urn:uuid:{restId(e)}</id>
    <title>{e.description.is}</title>
    <updated>{restTimestamp(e)}</updated>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
        <table>
          <tr><th>Amount</th><th>Tags</th><th>Receipt</th></tr>
          <tr><td>{e.amount.is.toString}</td>
              <td>{e.tags.map(_.name.is).mkString(", ")}</td>
              <td>{
                 if (e.receipt.is ne null) {
                   <img src={"/image/" + e.id} />
                 } else Text("None")
               }</td></tr>
        </table>
      </div>
    </content>
  </entry>
Now that we have the format, we simply hook into our dispatch method to match a GET request on a URL like:
http://www.pocketchangeapp.com/api/account/<accound ID>
Refer to Listing 15.4.1↑ again to see this match.

15.7.1 An Example Atom Request

An example Atom reqeust/response cycle for a test account is shown in Listing 15.7.1↓. We’ve cut off the entries here for brevity.
An Example Atom Request and Response
Request:
http://www.pocketchangeapp.com/api/account/1
​
Response:
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Test</title>
  <id>urn:uuid:1</id>
  <updated>2010-10-06T00:00:00Z</updated>
  <entry>
      <id>urn:uuid:http://www.pocketchangeapp.com/api/expense/3</id>
      <title>Receipt test</title>
      <updated>2010-10-06T00:00:00Z</updated>
      <content type="xhtml">
        <div xmlns="http://www.w3.org/1999/xhtml">
          <table>
          <tr><th>Amount</th><th>Tags</th><th>Receipt</th></tr>
          <tr><td>12.00</td>
              <td>test, receipt</td>
              <td><img src="/image/3" /></td></tr>
          </table>
        </div>
      </content>
    </entry>
    ...

15.7.2 Add a feed tag for the account page

As an extra nicety, we want to add an appropriate Atom <link/> tag to our Account view page so that people can easily subscribe to the feed from their browser. We do this by making two modifications to our template and snippet code. Listing 15.7.2↓ shows how we insert a new binding point in our viewAcct.html template to place the new link in the page head section.
Adding a binding to viewAcct.html
...
<lift:Accounts.detail eager_eval="true">
  <head><acct:atomLink /></head>
...
Listing 15.7.2↓ shows how we generate a new Atom link based on the current Account’s id that points to the proper URL for our API.
Binding the Atom link
bind("acct", xhtml,
     "atomLink" -> <link href={"/api/account/" + acct.id} 
                      type="application/atom+xml" 
                      rel="alternate" title={acct.name + " feed"} />,
     "name" -> acct.name.asHtml,
     ...

15.8 Conclusion

In this chapter, we outlined a RESTful API for a web application and showed how to implement one using Lift. We then extended that API to return Atom in addition to XML and JSON.
Up: Part II

(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir