원문 : http://simply.liftweb.net/index-Chapter-5.html#toc-Chapter-5
5장
HTTP와 REST (HTTP and REST)
Lift의 HTML 생성 특징들을 보았습니다. 이제 낮은 레벨과 REST스타일의 HTTP요청을 다루는 데 뛰어들어보겠습니다. 이 챕터를 위한 코드는 https://github.com/dpp/simply_lift/tree/master/samples/http_rest 에서 볼 수 있습니다.
5.1 소개
Lift는 세션의 범위내 또는 범위밖에서 모두 저레벨 HTTP요청에 접근하도록 해줍니다. 세션이 없거나 상태가 없는 모드에서 Lift는 HTTP응답에 쿠키를 추가하려고 컨테이너의 세션관리 장치를 사용하지 않고 요청동안에 이용가능한 SessionVar이나 ContainerVar를 만들지 않습니다. 무상태(stateless) REST 요청은 session affinity을 요구하지 않습니다. 무상태 REST 핸들링을 위한 인증은 OAuth를 통해서 이루어질 수 있습니다. 요청이 상태를 가지고 다루어진다면 컨테이너 세션은 JSESSIONID 쿠키가 요청의 일부로 제공되지 않았을 때 생성될 것이고 JSESSIONID 쿠키는 응답에 포함될 것입니다.
Lift는 들어오는 HTTP요청을 매칭하고 패턴매칭 프로세스의 일부로써 값들을 추출하고 결과를 리턴하도록 Scala의 패턴매칭을 사용합니다. Scala의 패턴매칭은 아주아주 강력합니다. 반드시 매치되어야 하는 패턴과 와일드카드 값(서브표현식은 모든 공급된 값과 매치될것입니다.)과 와일드카드값을 변수로 추출하고 명시적인 추출자(extractor)(매치될 것인지 변수로 추출한 것인지를 결정하려고 값에 명령적인 로직을 적용합니다.)의 선언을 모두 허용합니다. Lift는 HTTP요청을 나타하는 주어진 Req를 위해서 정의되었는지 보기 위해서 Scala의 PartialFunction[Req, () => Box[LiftResponse]]를 테스트합니다. 매치되었다면 Lift는 함수의 결과를 취하고 Box[LiftResponse]를 얻기위해서 그것을 적용하고 Box가 꽉찼다면 응답은 브라우저에 다시 보내질 것입니다. 예제를 보겠습니다.
5.2 고생스러운 REST(REST the hard way)
Lift와 함께 로우레벨의 REST를 수행하는 것을 보도록 하겠습니다: 들어오는 HTTP 요청을 취하고 그것은 Box[LiftResponse]를 리턴하는 함수로 변환합니다.(그리고 걱정하지 않아도 됩니다. 이것을 얻는 것은 더 쉽지만 보이지 않는 곳에서 무슨일이 벌어지는지에 대한 아이디어를 얻기 위해서 간단한 것으로 시작하겠습니다.)
BasicExample.scala:
퍼즐의 추가적인 조각은 핸들러를 후킹하는 것입니다. 이것은 다음 코드처럼 Boot.scala에서 이루어집니다.
코드를 보겠습니다. 먼저 각 핸들러는 PartialFunction[Req, () => Box[LiftResponse]]이지만 부분함수(partial function)의 별칭인 Scala 타입인 LiftRules.dispatchPF의 약칙을 사용할 수 있습니다.
요청 디스패처 핸들러의 타입 시그니처를 가진 findItem을 정의합니다.
매칭을 위해서 패턴을 정의합니다. 이 경우에 처음 두 단계가 /simple/item을 가진 어떤 3단계의 경로가 매치됩니다. 경로의 3번째부분은 변수 itemId로 추출될 것입니다. 마지막 경로 아이템의 접미사는 suffix 변수로 추출될 것이고 요청은 반드시 GET이어야 합니다.
위의 기준(criteria)을 만났다면 그 다음 부분함수(partial function)이 정의되고 Lift는 () => Box[LiftResponse]의 결과를 얻기 위해서 부분함수를 적용할 것입니다.
이것은 itemId를 찾고 결과 Item을 요청 접미사를 기반으로 응답으로 변환하는 함수입니다. toResponse 메서드는 다음과 같습니다:
약간 장황하더라도 아주 직설적입니다. 이 파일에서 다른 예제를 보겠습니다. 요청경로의 세번째 아이템의 String를 Item으로 변환하려고 추출자(extractor)를 사용합니다.
이 경우에 경로의 세번째 엘리먼트가 유효한 Item이 아닌 한 패턴은 매치되지 않을 것입니다. 만약 유효하다면 item변수는 프로세싱을 위해서 Item을 가지고 있을 것입니다. 이것은 유효한 응답으로 변환하는 것은 다음과 같습니다:
어떻게 추출이 동작하는지 보기위해서 obejct Item의 unapply메서드를 보겠습니다:
사실, 전체 Item코드를 보겠습니다. Simply Lift가 약속한 것처럼 명시적으로 퍼시스턴스는 포함하지 않습니다. 이 클래스는 메모리내의 목(mock) 퍼시스턴스 클래스이지만 Lift에서 어떤 다른 퍼시스턴스 메카니즘과 같이 동작합니다.
리스팅 5.2: Item.scala
결과 아웃풋이 무엇인지 보겠습니다:
5.3 RestHelper로 쉽게 만들기
앞의 예제는 Lift가 REST 호출들을 어떻게 다루는 지 보여줍니다. 하지만 약간 장황합니다. Lift의 RestHelper 트레이트는 읽기 쉽고 유지보수하기 쉽게 코드를 더 간결하게 만들어주는 아주 유용한 숏컷들을 많이 담고 있습니다. 한 뭉치의 예제들을 본 다음 각각을 보겠습니다:
리스팅 5.3: BasicWithHelper.scala
첫번째 것은 RestHelper기반의 서비스를 어떻게 선언하고 등록하는가 입니다.
우리의 BaseicWithHelper 싱글톤은 net.liftweb.http.rest.RestHelper 트레이트를 상속받습니다. Boot.scala에서 디스패치를 등록합니다.
이것은 전체 BasicWithHelper 싱글톤이 그것을 내부에 탐고 있는 모든 서브패턴들을 모으는 PartialFunction[Req, () => Box[LiftResponse]]라는 것을 의미합니다.
이제 다음을 분석해 보겠습니다:
위는 /simple3/item/xxx를 매치하고 xxx는 itemId 변수로 추출됩니다. 또한 요청은 JSON을 호출하는 Accepts 헤더를 가져야 합니다.
패턴이 매치되면 다음 코드를 실행합니다.
Box[LiftResponse]를 돌려주는 함수를 명시적으로 생성하지 않았다는 것을 기억해야 합니다. 대신에 타입은 Box[JValue]입니다. RestHelper는 Box[JValue]에서 () => Box[LiftResponse]로 함축적 컨버전(implicit conversion)을 제공합니다. 명확하게 Box가 Failure이면 RestHelper는 404의 바디로써 Failure 메시지를 가진 404 응답을 생성할 것입니다. Box가 Full이면 RestHelper는 페이로드(payload)내의 값을 가진 JsonResponse를 생성할 것입니다. 두가지 경우를 보겠습니다:
XML 예제는 RestHelper가 XmlResponse로 변환하는 Box[Node]로 응답을 바꾼다는 것 외에는 완전히 동일합니다.
다음은 결과입니다:
server 블럭내에 정의하였고 JValue와 Node를 올바른 응답타입으로의 변환이 처리되기 때문에 더 간단합니다. 단지 함축적 컨버전이 정의된 곳에 대해서 명식되어야 하고 그것들은 Item 싱글톤 내에 있습니다.
그래서 우리는 더 간단한 REST를 할 수 있습니다. 어떻게 더욱 간단하게 만들수 있는지에 대한 예제를 계속 보겠습니다. 이 예제는 명시적으로 Item.find보다는 추출자(extractor)를 사용합니다.
만약 DRY를 좋아하고 같은 경로 접두사들이 반복되는 것을 원하지 않는다면 예제처럼 prefix를 사용할 수 있습니다:
위의 코드는 아래에서 보이는 것 처럼 /simple5/item에 대한 응답에서 모든 아이템을 리스팅할 것이고 /simple5/item/1235에 대한 응답으로 특정한 아이템을 제공할 것입니다.
위의 예제에서 요청타입에 따라 명시적으로 결과를 JValue나 Node로 바꿉니다.Lift에서는 요청타입에 기반하여 주어진 타입을 응답타입(기본 응답타입은 JSON과 XML입니다)으로 바꾸는 변환을 정의하는 것이 가능하고 그 다음 매치하기 위한 요청 패턴을 정의하고 RestHelper가 rest를 처리합니다. Item에서 JVaue와 Node로의 변환을 정의하겠습니다.(implicit 키워드는 변환이 serveJx 문에서 이용가능하다는 것을 나타냅니다.)
이것은 아주 직설적입니다. 그것이 JsonSelect라면 JValue를 리턴하고 XmlSelect라면 Node로 변환합니다.
이것은 serveJx문에서 사용됩니다.
그래서 /simple6/item/1235는 매치될 것이고 Item을 리턴하고 함축적 컨버전에 기반해서 Item을 JValue나 Node로 Accepts헤더에 따라 바꿀 것입니다. 그 다음 () => Box[LiftResponse]로 변환합니다. curl로 어떤 결과인지 보겠습니다.
또한 /simple6/item/other/1234는 올바른 것을 한다는 것을 알아야 합니다. 이것은 경로가 4개 엘리먼트로 길기때문에 패턴의 첫부분에 매치되지 않지만 두번째 부분에 매치됩니다.
마지막으로 serveJx를 합치고 이것이 DRY 헬퍼인 prefixJx입니다.
5.4 완전한 REST 예제
앞의 코드는 완전히 제구실을 할 REST 서비스로 합칠수 있는 조각들로 제공되었습니다. 합치는 작업을 하고 어떠한 서비스인지 보겠습니다:
리스팅 5.4: FullRest.scala
전체 서비스는 JSON만 사용하고 단일 serve블락을 가지고 있으며 prefix 헬퍼를 서비스의 일부로써 /api/item하의 모든 요청을 정의하려고 사용합니다.
처음 2패턴은 무엇을 이미 커버하고 있는지 재해쉬(re-hash)합니다.
다음은 /api/item/search에 있는 검색부분입니다. 재미있는 작은 스칼라 라이브러리를 사용해서 search엘리먼트 다음에 오는 요청 경로 엘리먼트들의 리스트를 생성하고 모든 쿼리파라미터들은 q로 명명됩니다. 이것들에 기반해서 검색부분과 매치되는 모든 Item들을 검색합니다.
결과가 Lift[Item]이 되고 distinct로 중복부분을 제거하고 마지막으로 Lift[Item]을 JValue로 바꿉니다.
다음으로 Item을 어떻게 삭제하는지 보겠습니다:
실제로 다른 것은 JsonDelete HTTP요청을 찾는 것 뿐입니다.
PUT으로 Item을 어떻체 추가하는지 보겠습니다:
JsonPut뒤에 Item(item) -> _를 주목하세요. JsonPut에 대한 시그니처 추출은 (List[String], (JValue, Req))입니다. List[String]부분은 간단합니다... 그것은 요청경로를 담고 있는 List입니다. Pair의 두번째 부분은 JValue와 기초를 이루는 Req(이 경우 요청 그 자체와 함께 무언가를 해야할 필요가 있습니다.)를 담고 있는 Pair 그 자체입니다. Item 싱글톤의 def unapply(in: JValue): Option[Item]메서드가 있기 때문에 PUT 요청 바디로부터 만들어진 JValue를 추출(패턴매칭)할 수 있습니다. 이것은 사용자가 패턴매칭이 될 Item으로 변환될 수 있는 JSON blob을 PUT하는 것을 의미하고 인벤토리에 Item을 추가하는 case문의 우변을 평가할 것입니다. 그것은 크고 빽빽한 정보의 더미입니다. 그래서 POST로 다시 시도할 것입니다.
이 경우에 POST바디에 파싱가능한 JSON을 가지고 있는 /api/item/1234상의 POST를 매치합니다. mergeJson메서드는 찾아낸 Item의 모든 필드들을 취하고 POST바디의 JSON에 있는 필드들로 교체합니다. 그래서 {"qnty": 123} 의 POST 바디는 Item의 qnty필드를 교체할 것입니다. Item은 저장소(backing store)에 추가됩니다.
Cool. 그래서 우리의 REST서비스에서 GET, DELETE, PUST, POST 지원의 변화를 가졌습니다. 모두 패턴을 사용해서 RestHelper가 제공하는 것입니다.
여기서 약간 재미있는 것이 있습니다.
Lift의 HTML부분의 특징들중 하나는 Comet(long-polling을 통한 서버 푸쉬)에 대한 지원입니다. 웹 컨테이너가 그것을 지원한다면 Lift는 자동적으로 비동기적인 지원을 사용할 것입니다. 이것은 long poll중에 요청의 서비스와 연관되어 수행되고 있는 계산이 없더라도 소비될 쓰레드가 없다는 것을 의미합니다. 이것은 아주 많은 수의 롱폴링 클라이언트를 허용합니다. Lift의 REST지원은 비동기적인 지원을 포함합니다. 이 경우네 HTTP요청이 /api/item/change로 열리고 저장소의 변화를 기다리는 것을 보여주겠습니다. 요청은 저장소의 변화나 110초후에 JSON JNull로 만족될 것입니다.
/api/item/change에 대한 GET요청을 받았을 때 RestContinuation.async를 호출합니다. 호출을 설정하는 클로져를 건네줍니다. 110초후에 보내질 JNull 스케쥴링에 의한 호출을 설정합니다. 또한 저장소에서 변경사항이 생겼을때 호출되는 함수를 등록합니다. 이벤트(110초가 지나거나 저장소가 변경되었을때)가 발생했을때 함수들은 호출될 것이고 continuation을 호출하고 클라이언트에게 응답을 다시 보낼 satifyRequest 함수를 적용할 것입니다. 이 메카니즘을 사용해서 서버에서 쓰레드를 소비하지 않는 롱폴링 서비스를 만들 수 있습니다. satifyRequest 함수가 한번 호출되면 그것을 많이 호출할 수 있지만 오직 첫번째만 카운트 된다는 것을 유념하세요.
5.5 결론
이번 장에서 Lift에서 어떻게 웹서비스를 생성하는 지를 보았습니다. RestHelper가 커버하는 부분하에 많은 함축적 컨버전이 있어서 결과 코드는 아주 읽고, 만들고, 유지하기 쉽습니다. 코어부분에서 패턴에 대해서 패턴이 매치된다면 들어오는 요청을 매치하고 패턴의 우변의 표현식을 평가합니다.
Comments