REST Web Services
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 // stateful -- associated with a servlet container session LiftRules.statelessDispatchTable.append // stateless -- no session created
Within your MyRest object, you can define which URLs to serve:
serve { case Req => <b>Static</b> case Req => JString }
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 => <b>Static</b> case JsonGet => JString }
The above can also be written:
serve { case "api" :: "static" :: _ XmlGet _=> <b>Static</b> case "api" :: "static" :: _ JsonGet _ => JString }
Note: If you want to navigate your Web Service, you must remember to add a *.xml or *.json 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 }
And with extractors, we convert an element to a particular type and only succeed
with the pattern match if the parameter can be converted. For example:
serve { case "api" :: "user" :: AsLong :: _ XmlGet _ => <b>ID: {id}</b> case "api" :: "user" :: AsLong :: _ JsonGet _ => JInt }
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.map { u => u.save; u.toXml} case "api" :: "user" :: _ JsonPut json -> _ => // json is a net.liftweb.json.JsonAST.JValue User.createFromJson.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 => c.toJson case => 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 :: _, _) => Full }
Or:
// extract the parameters, create a user // return the appropriate response def addUser(): Box[UserInfo] = for { firstname <- S.param ?~ "firstname parameter missing" ~> 400 lastname <- S.param ?~ "lastname parameter missing" email <- S.param ?~ "email parameter missing" } yield { val u = User.create.firstName. lastName.email S.param foreach u.password.set u.saveMe } serveJx { case Post => 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”.
Issues
When using chained extractors such as this, it’s easy to hit a Scala bug in which the Scala compiler generates a method that’s too large for Java. The error looks like:
java.lang.Error: ch.epfl.lamp.fjbg.JCode$OffsetTooBigException: offset too big to fit in 16 bits: 38084
To work around the problem, you need to break your selectors into multiple serve statements.
serve { case "api" :: "user" :: AsLong :: _ XmlGet _ => <b>ID: {id}</b> } serve { case "api" :: "user" :: AsLong :: _ JsonGet _ => JInt }
Or you can use the 2.3 “prefix” feature where you can specify the URL prefix before the partial function/pattern match:
/** * A full REST example */ object FullRest extends RestHelper { // Serve /api/item and friends serve( "api" / "item" prefix { // /api/item returns all the items case Nil JsonGet _ => Item.inventoryItems: JValue // /api/item/count gets the item count case "count" :: Nil JsonGet _ => JInt // /api/item/item_id gets the specified item case Item :: Nil JsonGet _ => item: JValue // /api/item/search/foo or /api/item/search?q=foo case "search" :: q JsonGet _ => (for { searchString <- q ::: S.params item <- Item.search } yield item).distinct: JValue // DELETE the item in question case Item :: Nil JsonDelete _ => Item.delete.map // PUT adds the item if the JSON is parsable case Nil JsonPut Item -> _ => Item.add: JValue // POST if we find the item, merge the fields from the // the POST body and update the item case Item :: Nil JsonPost json -> _ => Item(mergeJson).map(Item.add: JValue) // Wait for a change to the Items // But do it asynchronously case "change" :: Nil JsonGet _ => RestContinuation.async { f => { // schedule a "Null" return if there's no other answer // after 110 seconds Schedule.schedule(() => f, 110 seconds) // register for an "onChange" event. When it // fires, return the changed item as a response Item.onChange(item => f) } } }) }
Comments are disabled for this space. In order to enable comments, Messages tool must be added to project.
You can add Messages tool from Tools section on the Admin tab.