관리 메뉴

HAMA 블로그

[Play2] WebSockets 본문

PlayFramework2

[Play2] WebSockets

[하마] 이승현 (wowlsh93@gmail.com) 2016. 10. 13. 10:19

WebSockets


WebSockets 는 웹 브라우저에서 양방향 통신을 가능케 하는 프로토콜 기반으로 사용 될 수 있습니다. (역주: 서버쪽에서 웹브라우저쪽으로도 메세지를 보낸다는 뜻이죠. 기존에는 브라우저에서 서버쪽으로 요청하는 폴링을 주로 사용 했었음) 클라이언트는 메세지를 보낼 수 있으며 서버는 언제라도 메세지를 받을 수 있습니다. 물론 그들 사이에 WebSocket 연결이 액티브 상태일 동안 말이죠. 

현재 HTML5 구현이 된 웹 브라우저들은 대부분 자바스크립트 웹소켓 API 를 통한 WebSockets를 지원 하고 있습니다. 그러나 웹소켓이 오직 웹 브라우저에서만 그 의미를 갖는 것은 아닙니다. 많은 웹소켓 클라이언트 라이브러리들을 활용 할 수 있으며 예를들어 서버가 다른 서버와 통신하기 위해서도 사용 할 수 있습니다네이티브 모바일 앱과도 말이죠. WebSockets 을 여기서 사용하는것은 플레이 서버가 사용하는 기존 TCP 포트를 재사용할 수 있는 이득을 가져다 줍니다. 

WebSockets 조작

지금까지 우리는 Action 인스턴스를 표준 HTTP 요청과 응답을 위해서 사용 하였습니다. 
WebSockets 은 전체적으로 다르게 사용되며 표준 Action 을 경유해서 조작되지 않습니다.

플레이는 두가지 다른 방식으로 웹 소켓을 지원하는데 첫번째는 액터를 사용하는것이고 두번째는 iteratees 를 사용하는 것입니다. 양쪽 모두WebSocket 에서 제공된 빌더를 사용합니다.

1. WebSockets 을 액터와 함께 조작하기 


WebSocket 의 acceptWithActor 메소드를 사용하여 웹소켓 통신 할 수 있는 환경을 만듭니다. 사용자 정의 액터에  props 의 매개변수로 전달 된 out 즉 외부에 메세지를 전송 할 수 있는 연결 통로를 제공합니다.

import play.api.mvc._
import play.api.Play.current

def socket = WebSocket.acceptWithActor[String, String] { request => out =>
  MyWebSocketActor.props(out)
}

acceptWithActor 메소드의 내부는 아래와 같습니다. 궁금하실까봐..복잡합니다. @@

def acceptWithActor[In, Out](f: RequestHeader => HandlerProps)(implicit in: FrameFormatter[In],
out: FrameFormatter[Out], app: Application, outMessageType: ClassTag[Out]): WebSocket[In, Out] = {
tryAcceptWithActor { req =>
Future.successful(Right((actorRef) => f(req)(actorRef)))
}
}


MyWebSocketActor 의 구현은 다음과 같습니다 : 

import akka.actor._

object MyWebSocketActor {
  def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}

class MyWebSocketActor(out: ActorRef) extends Actor {
  def receive = {
    case msg: String =>
      out ! ("I received your message: " + msg)
  }
}

클라이언트에서 보낸 메세지는 사용자 정의 액터에게 보내 질 것입니다. 그리고 다시 메세지를 돌려 줄 수 있습니다. 위의 액터는 메시지를받은 상태에서 클라이언트에서받은 모든 메시지를 간단히 보냅니다.


2. WebSockets 을 iteratees 로 조작하기

액터는 분리 된 메시지를 처리하기위한 더 나은 추상화이지만, iteratees 는 스트림 처리 에있어 더 좋은 추상화입니다. WebSocket 요청을 처리하려면 Action 대신 WebSocket을 사용합니다.

import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext

def socket = WebSocket.using[String] { request =>
  // Log events to the console
  val in = Iteratee.foreach[String](println).map { _ =>
    println("Disconnected")
  }

  // Send a single 'Hello!' message
  val out = Enumerator("Hello!")

  (in, out)
}

WebSocket은 요청 헤더 (WebSocket 연결을 시작하는 HTTP 요청)에 액세스 할 수 있으므로 표준 헤더와 세션 데이터를 검색 할 수 있습니다. 그러나 요청 본문이나 HTTP 응답에는 액세스 할 수 없습니다. 이 방법으로 WebSocket을 구성 할 때 우리는 in과 out 채널을 모두 반환해야합니다.

 - in 채널은 각 메시지에 대해 통지 될 Iteratee [A, Unit] (여기서 A는 메시지 유형 - 여기서 우리는 String을 사용함)이며 소켓이 클라이언트 측에서 닫힐 때 EOF를 수신합니다.

- out 채널은 웹 클라이언트에 보낼 메시지를 생성하는 열거 자 [A]입니다. EOF를 전송하여 서버 측의 연결을 닫을 수 있습니다.

이 예제에서는 각 메시지를 콘솔에 출력하는 간단한 iteratee를 생성합니다. 메시지를 보내려면 간단한 더미 enumerator 를 만들어 하나의 Hello! 메시지를 전송하세요.

Tip: WebSockets 은  https://www.websocket.org/echo.html. 여기서 테스트 가능합니다.  위치는 그냥 ws://localhost:9000. 로 설정하시구요.


Hello!를 전송 한 직후 입력 데이터를 버리고 소켓을 닫는 다른 예제를 작성해 보겠습니다. 

import play.api.mvc._
import play.api.libs.iteratee._

def socket = WebSocket.using[String] { request =>

  // Just ignore the input
  val in = Iteratee.ignore[String]

  // Send a single 'Hello!' message and close
  val out = Enumerator("Hello!").andThen(Enumerator.eof)

  (in, out)
}


다음은 입력 데이터가 표준 출력에 기록되고 Concurrent.broadcast를 사용하여 클라이언트에 브로드 캐스팅되는 다른 예입니다.

import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext

def socket =  WebSocket.using[String] { request =>

  // Concurrent.broadcast returns (Enumerator, Concurrent.Channel)
  val (out, channel) = Concurrent.broadcast[String]

  // log the message to stdout and send response back to client
  val in = Iteratee.foreach[String] {
    msg =>
      println(msg)
      // the Enumerator returned by Concurrent.broadcast subscribes to the channel and will
      // receive the pushed messages
      channel push("I received your message: " + msg)
  }
  (in,out)
}


Comments