11장
REST
Lift는 REST 스타일의 웹서비스를 아주 간단히 제공하도록 해줍니다.
우선, RestHelper를 상속받은 객체를 생성합니다:
import net.liftweb.http._
import net.liftweb.http.rest._
object MyRest extends RestHelper {
}
그리고 Boot.scala에서 Lift에 변화를 후킹(hook)합니다.
LiftRules.dispatch.append(MyRest) // stateful - 서블릿컨테이너 세션과 연결된
LiftRules.statelessDispatchTable.append(MyRest) // stateless - 세션이 생성되지 않습니다
MyRest 오브젝트내에서 제공하는 URL들을 정의할 수 있습니다.
serve {
case Req("api" :: "static" :: _, "xml", GetRequest) => <b>Static</b>
case Req("api" :: "static" :: _, "json", GetRequest) => JString("Static")
}
위의 코드는 응답타입을 결정하기 위해서 요청의 접미사를 사용합니다. Lift는 응답 타입을 위해서 Accept 헤더를 테스트하는 것을 지원합니다:
serve {
case XmlGet("api" :: "static" :: _, _) => <b>Static</b>
case JsonGet("api" :: "static" :: _, _) => JString("Static")
}
또한 위의 코드는 아래처럼 작성될 수 있습니다:
serve {
case "api" :: "static" :: _ XmlGet _=> <b>Static</b>
case "api" :: "static" :: _ JsonGet _ => JString("Static")
}
Note: 여러분의 웹서비스를 네비게이션하기를 원한다면 URL의 끝에 *.xml이나 *.json을 추가해야 한다(여러분이 구현한 것에 따라)는 것을 기억해야 합니다. : http://localhost:8080/XXX/api/static/call.json http://localhost:8080/XXX/api/static/call.xml
REST 디스패치(dispatch) 코드는 Scala의 패턴매칭에 기반하기 때문에 요청으로부터 엘리먼트를 추출할 수 있습니다.(이 경우에서 세번째 엘리먼트는 문자열인 id 변수로 추출될 것입니다.)
serve {
case "api" :: "user" :: id :: _ XmlGet _ => <b>ID: {id}</b>
case "api" :: "user" :: id :: _ JsonGet _ => JString(id)
}
그리고 익스트랙터(extractor)와 함께 엘리먼트를 특정한 타입으로 변환하고 오직 파라미터가 변환될 수 있는 경우에만 패턴매칭(과 디스패치)이 성공합니다. 예를 들면:
serve {
case "api" :: "user" :: AsLong(id) :: _ XmlGet _ => <b>ID: {id}</b>
case "api" :: "user" :: AsLong(id) :: _ JsonGet _ => JInt(id)
}
위의 예제에서 Long으로 변환될 수 있다면 id는 추출됩니다.
또한 Lift의 REST 헬퍼는 POST나 PUT요청으로부터 XML이나 JSON을 추출할 수 있고 XML이나 JSON이 유효한 경우에만 요청을 디스패치할 수 있습니다:
serve {
case "api" :: "user" :: _ XmlPut xml -> _ => // xml은 scala.xml.Node입니다
User.createFromXml(xml).map { u => u.save; u.toXml}
?
case "api" :: "user" :: _ JsonPut json -> _ => // json은 net.liftweb.json.JsonAST.JValue입니다
User.createFromJson(json).map { u => u.save; u.toJson}
}
값을 계산하는 비즈니스 로직의 단일조각을 갖기 원하는 때의 경우들이 있을 것이지만 그다음 값을 요청타입에 기반한 결과로 변환합니다. 그것이 serveJx가 온 곳입니다... JSON과 XML 요청을 위한 응답이 제공될 것입니다. Convertable라는 trait를 정의한다면:
trait Convertable {
def toXml: Elem
def toJson: JValue
}
그 다음 Convertable을 JSON이나 XML로 변환할 패턴을 정의합니다.
implicit def cvt: JxCvtPF[Convertable] = { case (JsonSelect, c, _) => c.toJson case (XmlSelect, c, _) => c.toXml }
그리고 serveJx와 Box[Convertable]에서 패턴결과를 사용하는 어느 곳에서나 cvt패턴은 적절한 응답을 생성하기 위해서 사용됩니다:
serveJx {
case Get("api" :: "info" :: Info(info) :: _, _) => Full(info)
}
또는 :
// 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()
}
위의 예제에서 firstname파라미터가 없다면 응답은 “firstname parameter missing”이라는 응답 바디를 가진 400이 될 것입니다. lastname 파라미터가 없다면 응답은 “lastname parameter missing” 응답바디를 가진 404가 될 것입니다.
어느새 11장까지 보셨군요; (헉! 언제 따라가지..)
중간에 없는 장이 많거든요.. ㅎㅎㅎ
포스팅에 집중해서 저도 다시 봐야되요 ㅋㅋ