Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Simply Lift : Chapter 11 - REST

원문 : http://simply.liftweb.net/index-Chapter-11.html#toc-Chapter-11

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가 될 것입니다.
2011/03/09 00:42 2011/03/09 00:42