Up: Part I

11 REST

Lift makes providing REST-style web services very simple.
First, create an object that extends RestHelper:
import net.liftweb.http._
import net.liftweb.http.rest._
​
object MyRest extends RestHelper {
  
}
And hook your changes up to Lift in Boot.scala:
LiftRules.dispatch.append(MyRest) // stateful — associated with a servlet container session
LiftRules.statelessDispatchTable.append(MyRest) // stateless — no session created
Within your MyRest object, you can define which URLs to serve:
serve { 
  case Req("api" :: "static" :: _, "xml", GetRequest) => <b>Static</b> 
  case Req("api" :: "static" :: _, "json", GetRequest) => JString("Static") 
}
The above code uses the suffix of the request to determine the response type. Lift supports testing the Accept header for a response type:
serve { 
  case XmlGet("api" :: "static" :: _, _) => <b>Static</b> 
  case JsonGet("api" :: "static" :: _, _) => JString("Static") 
}
The above can also be written:
serve { 
  case "api" :: "static" :: _ XmlGet _=> <b>Static</b> 
  case "api" :: "static" :: _ JsonGet _ => JString("Static") 
} 
Note: If you want to navigate your Web Service, you must remember to add a *.xml or *.json (depending in what you have implemented) at the end of the URL: http://localhost:8080/XXX/api/static/call.json http://localhost:8080/XXX/api/static/call.xml
Because the REST dispatch code is based on Scala’s pattern matching, we can extract elements from the request (in this case the third element will be extracted into the id variable which is a String:
serve { 
  case "api" :: "user" :: id :: _ XmlGet _ => <b>ID: {id}</b>
  case "api" :: "user" :: id :: _ JsonGet _ => JString(id) 
} 
And with extractors, we convert an element to a particular type and only succeed with the pattern match (and the dispatch) if the parameter can be converted. For example:
serve { 
  case "api" :: "user" :: AsLong(id) :: _ XmlGet _ => <b>ID: {id}</b>
  case "api" :: "user" :: AsLong(id) :: _ JsonGet _ => JInt(id) 
} 
In the above example, id is extracted if it can be converted to a Long.
Lift’s REST helper can also extract XML or JSON from a POST or PUT request and only dispatch the request if the XML or JSON is valid:
serve { 
  case "api" :: "user" :: _ XmlPut xml -> _ => // xml is a scala.xml.Node 
    User.createFromXml(xml).map { u => u.save; u.toXml}
​
  case "api" :: "user" :: _ JsonPut json -> _ => // json is a net.liftweb.json.JsonAST.JValue
    User.createFromJson(json).map { u => u.save; u.toJson} 
} 
There may be cases when you want to have a single piece of business logic to calculate a value, but then convert the value to a result based on the request type. That’s where serveJx comes in … it’ll serve a response for JSON and XML requests. If you define a trait called Convertable:
trait Convertable {
  def toXml: Elem 
  def toJson: JValue 
}
Then define a pattern that will convert from a Convertable to a JSON or XML:
implicit def cvt: JxCvtPF[Convertable] = { case (JsonSelect, c, _) => c.toJson case (XmlSelect, c, _) => c.toXml }
And anywhere you use serveJx and your pattern results in a Box[Convertable], the cvt pattern is used to generate the appropriate response:
serveJx { 
  case Get("api" :: "info" :: Info(info) :: _, _) => Full(info) 
}
Or:
// extract the parameters, create a user 
// return the appropriate response 
​
def addUser(): Box[UserInfo] = 
  for { 
    firstname <- S.param("firstname") ?~ "firstname parameter missing" ~> 400 
    lastname <- S.param("lastname") ?~ "lastname parameter missing" 
    email <- S.param("email") ?~ "email parameter missing" 
  } yield { 
    val u = User.create.firstName(firstname). 
      lastName(lastname).email(email)
​
    S.param("password") foreach u.password.set
    u.saveMe 
  }
​
serveJx {
  case Post("api" :: "add_user" :: _, _) => addUser() 
}
In the above example, if the firstname parameter is missing, the response will be a 400 with the response body “firstname parameter missing”. If the lastname parameter is missing, the response will be a 404 with the response body “lastname parameter missing”.
Up: Part I

(C) 2012 David Pollak