Spray.io尝试
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>