Spray.io搭建Rest — 支持WebSocket

Spray.io尝试

  • 使用Spray.io搭建Rest服务
  • Spray.io搭建Rest — 支持Twirl模板并部署
  • Spray.io搭建Rest — 支持WebSocket


Spray.io搭建Rest — 支持WebSocket

工程地址:http://git.oschina.net/for-1988/Simples.git
    最近在看spray就是想用它来做WebSocket的后台,今天就研究了一下spray怎么支持WebSocket。参考了一些老外的代码,WebSocket的协议的实现用了 Java-WebSocket这个开源项目。

添加JAR依赖

    在build.sbt文件中添加Java-WebSocket的依赖。我这里这个包通过sbt下载不了,所以就直接放在工程中引入了。

"org.java-websocket"  %   "Java-WebSocket" % "1.3.1"

WebSocket服务

    我们借助Java-WebSocket来实现WebSocket的相关消息事件的处理,它为我们做了很多的封装,只需传入java.net.InetSocketAddress对象即可。然后把不同的消息封装成对象,转发给对应的actor。这里都是运用了Akka。

object WSocketServer {

  sealed trait WSocketServerMessage

  case class Message(ws: WebSocket, msg: String)
    extends WSocketServerMessage

  case class BufferMessage(ws: WebSocket, buffer: ByteBuffer)
    extends WSocketServerMessage

  case class Open(ws: WebSocket, hs: ClientHandshake)
    extends WSocketServerMessage

  case class Close(ws: WebSocket, code: Int, reason: String, external: Boolean)
    extends WSocketServerMessage

  case class Error(ws: WebSocket, ex: Exception)
    extends WSocketServerMessage

}

class WSocketServer(val port: Int)
  extends WebSocketServer(new InetSocketAddress(port)) {
  private val reactors = Map[String, ActorRef]()

  final def forResource(descriptor: String, reactor: Option[ActorRef]) {
    reactor match {
      case Some(actor) => reactors += ((descriptor, actor))
      case None => reactors -= descriptor
    }
  }

  final override def onMessage(ws: WebSocket, msg: String) {
    if (null != ws) {
      reactors.get(ws.getResourceDescriptor) match {
        case Some(actor) => actor ! WSocketServer.Message(ws, msg)
        case None => ws.close(CloseFrame.REFUSE)
      }
    }
  }

  final override def onMessage(ws: WebSocket, buffer: ByteBuffer) {
    if (null != ws) {
      reactors.get(ws.getResourceDescriptor) match {
        case Some(actor) => actor ! WSocketServer.BufferMessage(ws, buffer)
        case None => ws.close(CloseFrame.REFUSE)
      }
    }
  }

  final override def onOpen(ws: WebSocket, hs: ClientHandshake) {
    if (null != ws) {
      val actor = reactors.get(ws.getResourceDescriptor)
      actor.get ! WSocketServer.Open(ws, hs)
    }
  }

  final override def onClose(ws: WebSocket, code: Int, reason: String, external: Boolean) {
    if (null != ws) {
      reactors.get(ws.getResourceDescriptor) match {
        case Some(actor) => actor ! WSocketServer.Close(ws, code, reason, external)
        case None => ws.close(CloseFrame.REFUSE)
      }
    }
  }

  final override def onError(ws: WebSocket, ex: Exception) {
    if (null != ws) {
      reactors.get(ws.getResourceDescriptor) match {
        case Some(actor) => actor ! WSocketServer.Error(ws, ex)
        case None => ws.close(CloseFrame.REFUSE)
      }
    }
  }
}

我们在这个对象中,通过Map来存储了每一个请求地址对应的处理Actor。

WebSocket的路由

1.在Routes中添加注册每一个WebSocket请求地址对应的处理Actor

trait Routes extends RouteConcatenation with StaticRoute with AbstractAkkaSystem {

  val httpServer = system.actorOf(Props(classOf[HttpServer], allRoutes))

  val socketServer = system.actorOf(Props[SocketServer])


  lazy val index = system.actorOf(Props[IndexActor], "index")

  lazy val allRoutes = logRequest(showReq _) {
    new IndexService(index).route ~ staticRoute
  }
  //注册WebSocket处理Actor
  implicit val wsocketServer: WSocketServer
  wsocketServer.forResource("/ws", Some(index))


  private def showReq(req: HttpRequest) = LogEntry(req.uri, InfoLevel)
}
2.启动WebSocket服务
object Server extends App with Routes {

  implicit lazy val system = ActorSystem("server-system")

  implicit lazy val wsocketServer = new WSocketServer(Configuration.portWs)
  //启动WebSocket服务
  wsocketServer.start
  sys.addShutdownHook({
    system.shutdown
    wsocketServer.stop
  })

  IO(Http) ! Http.Bind(httpServer, Configuration.host, port = Configuration.portHttp)
  // IO(Tcp) ! Tcp.Bind(socketServer, new InetSocketAddress(Configuration.host, Configuration.portTcp))
}

WebSocket处理Actor

我们在之前的IndexActor上加一下对WebSocket消息的处理,这里做了简单的接受消息,然后直接回复给客户端

class IndexActor extends Actor with ActorLogging {

  import WSocketServer._

  override def receive = {
    case Open(ws, hs) =>
      ws.send("Hello")
      log.debug("registered monitor for url {}", ws.getResourceDescriptor)
    case Message(ws, msg) =>
      log.debug("url {} received msg '{}'", ws.getResourceDescriptor, msg)
      ws.send("【echo】"+msg)
  }
}

客户端调用

    我们修改了之前的index模板,加入了WebSocket

@(name:String)
<html>
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>Hello @name !!!</h1>
<input id="message"/> <button onclick="javascript:send();">发送</button>
<div id="msg"></div>
</body>

<script>

    var wsurl = "ws://localhost:6696/ws";
    var ws = null;
    if ('WebSocket' in window) {
        ws = new WebSocket(wsurl);
    } else if ('MozWebSocket' in window) {
        ws = new MozWebSocket(wsurl + "?uid=" + uid);
    } else {
        console.error("初始化 Main websocket 对象失败!");
    }

    ws.onopen = function (event) {
        var msg = "Hi";
        ws.send(msg)
    }
    ws.onmessage = function (event) {
        console.info(event.data);
        var data = new Date();
        document.getElementById("msg").innerHTML += "<h5>"+data.toTimeString()+" : "+event.data+"</h5>";
    }

    function send(){
        var msg = document.getElementById("message").value;
        ws.send(msg);
    }
</script>
</html>
Spray.io搭建Rest — 支持WebSocket_第1张图片


你可能感兴趣的:(scala,akka,Spray.io)