Spray.io尝试
工程地址:http://git.oschina.net/for-1988/Simples.git最近在看spray就是想用它来做WebSocket的后台,今天就研究了一下spray怎么支持WebSocket。参考了一些老外的代码,WebSocket的协议的实现用了 Java-WebSocket这个开源项目。
在build.sbt文件中添加Java-WebSocket的依赖。我这里这个包通过sbt下载不了,所以就直接放在工程中引入了。
"org.java-websocket" % "Java-WebSocket" % "1.3.1"
我们借助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。
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)) }
我们在之前的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>