Outsider's Dev Story

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

Play framework에서 Long Polling 구현하기

Play Framework상에서 Comet을 구현할 일이 있었습니다. 
기존에 지속적인 갱신을 위해서 Polling이라고 불리는 일정 간격으로 서버에 요청을 보내서 바뀐 내용을 조회하는 방식을 많이 사용했지만 리얼타임수준으로 구현하려면 간격을 줄여야 하기 때문에 불필요한 요청이 많이 필요했고 실제 리얼타임이라고 하기도 어려웠습니다. 반면에 Comet은 Ajax Push라고도 알려져 있는데(최근에는 거의 Comet으로 통칭되는듯 합니다.) 특정 기술에 대한 이름이라기 보다는 HTTP상에서 Push를 하는 방식을 가리키는 의미로 사용됩니다.


세부구현은 다양하게 있지만 대표적으로는 Long Polling와 Streaming 방식이 있다고 할수 있습니다. 위 그림을 보면 쉽게 이해할 수 있는데 Polling이 요청을 보내면 응답을 바로 받는 대신에 Long Polling은 클라이언트가 요청을 보내면 서버에서 이벤트가 발생할 때까지(데이터가 갱신된다거나 하는 등) 응답을 하지 않고 가지고 있다고 이벤트가 발생하면 응답을 하고 클라이언트는 바로 다시 요청을 보내는 방식입니다. 스트리밍은 요청을 최초 한번 보내면 chunk데이터를 통해서 이벤트가 있을때마다 계속 클라이언트에 데이터를 보내줍니다.

일반적으로 서블릿 컨테이너는 요청당 쓰레드가 생기는 방식이기 때문에  위와같이 구현을 하면 서버가 클라이언트의 요청을 계속 유지하고 있기 때문에 서비스로 제공하기에 그리 적합하지는 않은 것으로 알고 있습니다. Play Framework는 Servlet을 사용하지 않고 있습니다. Play가 서블릿을 사용하지 않는 부분에 대해서는 Play의 창시자인 Guillaume Bort가 쓴 Why there is no servlets in Play과 Ike의 Can The Play Framework Play Nice With Others?에 좀 자세히 나와있습니다. Play가 Comet에 좀 더 적합한 아키텍쳐를 가지고 있는 지는 저도 파악중이라 따로 설명은 하지 않겠습니다.




Play-Push
Comet을 구현할 방법에 대해서 고민을 하다가 Leif Singer가 작성한 Play-Push라는 잘 된 데모를 찾게 되었고 굳이 따로 예제를 만들 필요가 없을듯 해서 그대로 사용하려고 합니다. Play-Push는 Elias Klughammer가 Cappuccino X Tornado라는 Tornado웹서버상에서 Cappuccino로 채팅애플리케이션을 만든 것을 보고 비슷하게 Play로 구현한 것입니다.


위 영상은 Cappuccino X Tornado의 영상이기는 하지만  Play-Push도 거의 동일하게 구현되어 있습니다.(배경화면을 공유하는 것만 현재 빠져있는것 같습니다.)



Play-Push에서 채팅서비스를 Long Polling을 사용해서 구현하였는데 해당 부분은 /app/controllers/Messages.java에 구현되어 있습니다.


public static void since(Long timestamp) {
    List<Message> ms = Message.find("ts > ?", timestamp).fetch();
    if ( ms.size() > 0 ) {
        renderJSON(ms);
    }
    suspend(50);
}
위 소스가 Long Polling을 처리해 주는 부분입니다. timestamp(일반적으로 디비를 사용한다면  시퀀스아이디가 되겠죠)를 파라미터로 받아서  Message에 파라미터로 받은 timestamp보다 최신값이 있는지 검사하고 있으면 결과를 렌더링해서 리턴하고 없으면  suspend(50)을 실행하여 50밀리세컨드 후에 다시 실행하게 됩니다. 클라이언트로 보낼 데이터가 있을때까지 suspend를 이용해서 응답하지 않은 상태로 계속 반복해서 확인을 하는 구조입니다. 클라이언트 부분은 Cappuccino로 구현되어 있기 때문에 보기 어려우면 그리 어렵지 않습니다.  Ajax로 서버측에  since를 호출하면서 파라미터로 timestamp를 넘겨주고  Ajax callback에서 결과를 돌려받은뒤 다시  Ajax 를 호출하는 방식으로 구현하기만 하면 됩니다.

이렇게 구현되면  Ajax요청이 발생하고 새로운  Message(혹은 새로운 데이터)가 생기기 전까지는 계속 기다리고 있는 상태가 되고 새로운 데이터가 생기면 바로(실제로는 50밀리세컨의 갭이 존재하지만 신경쓸정도는 아닌듯 합니다.) 클라이언트로 응답이 보내지고 클라이언트는 다시 새  Ajax요청을 보내고 응답을 기다리고 있는 상태가 됩니다. (HTTP 모니터링 툴을 보면 응답을 받은 뒤에 새 요청이 날라가고 계속 대기상태에 있는 것을 확인할 수 있습니다.)
2010/09/27 02:22 2010/09/27 02:22