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를 상속받은 객체를 생성합니다:

1import net.liftweb.http._
2import net.liftweb.http.rest._
3
4object MyRest extends RestHelper {
5}

그리고 Boot.scala에서 Lift에 변화를 후킹(hook)합니다.

1LiftRules.dispatch.append(MyRest) // stateful - 서블릿컨테이너 세션과 연결된
2LiftRules.statelessDispatchTable.append(MyRest) // stateless - 세션이 생성되지 않습니다

MyRest 오브젝트내에서 제공하는 URL들을 정의할 수 있습니다.

1serve {
2   case Req("api" :: "static" :: _, "xml", GetRequest) => <b>Static</b>
3   case Req("api" :: "static" :: _, "json", GetRequest) => JString("Static")
4}

위의 코드는 응답타입을 결정하기 위해서 요청의 접미사를 사용합니다. Lift는 응답 타입을 위해서 Accept 헤더를 테스트하는 것을 지원합니다:

1serve {
2   case XmlGet("api" :: "static" :: _, _) => <b>Static</b>
3   case JsonGet("api" :: "static" :: _, _) => JString("Static")
4}

또한 위의 코드는 아래처럼 작성될 수 있습니다:

1serve {
2   case "api" :: "static" :: _ XmlGet _=> <b>Static</b>
3   case "api" :: "static" :: _ JsonGet _ => JString("Static")
4}

Note: 여러분의 웹서비스를 네비게이션하기를 원한다면 URL의 끝에 *.xml이나 *.json을 추가해야 한다(여러분이 구현한 것에 따라)는 것을 기억해야 합니다. : http://localhost:8080/XXX/api/static/call.json http://localhost:8080/XXX/api/static/call.xml

REST 디스패치(dispatch) 코드는 Scala의 패턴매칭에 기반하기 때문에 요청으로부터 엘리먼트를 추출할 수 있습니다.(이 경우에서 세번째 엘리먼트는 문자열인 id 변수로 추출될 것입니다.)

1serve {
2   case "api" :: "user" :: id :: _ XmlGet _ => <b>ID: {id}</b>
3   case "api" :: "user" :: id :: _ JsonGet _ => JString(id)
4}

그리고 익스트랙터(extractor)와 함께 엘리먼트를 특정한 타입으로 변환하고 오직 파라미터가 변환될 수 있는 경우에만 패턴매칭(과 디스패치)이 성공합니다. 예를 들면:

1serve {
2   case "api" :: "user" :: AsLong(id) :: _ XmlGet _ => <b>ID: {id}</b>
3   case "api" :: "user" :: AsLong(id) :: _ JsonGet _ => JInt(id)
4}

위의 예제에서 Long으로 변환될 수 있다면 id는 추출됩니다.

또한 Lift의 REST 헬퍼는 POST나 PUT요청으로부터 XML이나 JSON을 추출할 수 있고 XML이나 JSON이 유효한 경우에만 요청을 디스패치할 수 있습니다:

1serve {
2   case "api" :: "user" :: _ XmlPut xml -> _ => // xml은 scala.xml.Node입니다
3      User.createFromXml(xml).map { u => u.save; u.toXml}
4?
5   case "api" :: "user" :: _ JsonPut json -> _ => // json은 net.liftweb.json.JsonAST.JValue입니다
6      User.createFromJson(json).map { u => u.save; u.toJson}
7}

값을 계산하는 비즈니스 로직의 단일조각을 갖기 원하는 때의 경우들이 있을 것이지만 그다음 값을 요청타입에 기반한 결과로 변환합니다. 그것이 serveJx가 온 곳입니다... JSON과 XML 요청을 위한 응답이 제공될 것입니다. Convertable라는 trait를 정의한다면:

1trait Convertable {
2   def toXml: Elem
3   def toJson: JValue
4}

그 다음 Convertable을 JSON이나 XML로 변환할 패턴을 정의합니다.

implicit def cvt: JxCvtPF[Convertable] = { case (JsonSelect, c, _) => c.toJson case (XmlSelect, c, _) => c.toXml }

그리고 serveJx와 Box[Convertable]에서 패턴결과를 사용하는 어느 곳에서나 cvt패턴은 적절한 응답을 생성하기 위해서 사용됩니다:

1serveJx {
2   case Get("api" :: "info" :: Info(info) :: _, _) => Full(info)
3}

또는 :

 1// extract the parameters, create a user
 2// return the appropriate response
 3
 4def addUser(): Box[UserInfo] =
 5   for {
 6      firstname <- S.param("firstname") ?~ "firstname parameter missing" ~> 400
 7      lastname <- S.param("lastname") ?~ "lastname parameter missing"
 8      email <- S.param("email") ?~ "email parameter missing"
 9   } yield {
10      val u = User.create.firstName(firstname).lastName(lastname).email(email)
11
12      S.param("password") foreach u.password.set
13      u.saveMe
14   }
15
16serveJx {
17   case Post("api" :: "add_user" :: _, _) => addUser()
18}

위의 예제에서 firstname파라미터가 없다면 응답은 “firstname parameter missing”이라는 응답 바디를 가진 400이 될 것입니다. lastname 파라미터가 없다면 응답은 “lastname parameter missing” 응답바디를 가진 404가 될 것입니다.

Valid HTML5 Valid CSS WCAG 2.1 AA tested