目次

Version 4, last updated by bontoJR at Nov 28 02:17 UTC

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”.