さて。会社の開発合宿にいってきました。(合宿の様子)
それで、僕のテーマは、Websocketにしてみたのですが、色々ハマった上で、おもしろいものができたっぽいので、エントリーしようかと思いました。
「websocket scala」でggrとでてくるid:yuroyoroさんの日記「Jetty7のWebSocketをScalaから使う」、大変参考にさせていただいたのですが、Jetty7を対象にかかれていて、ちょっと古くなっていました。今回これをベースにJetty8で動かすために奮闘したって感じです。
現物
何はさておき、今回作った動くものです。
動かし方(多分うごく)
git clone git@github.com:qtamaki/websocket-jetty-scala.git cd websocket-jetty-scala sbt > container:start
サーバーが起動したら、http://localhost:8080/にアクセスしてください。
Jetty8をsbtで使う
Jetty8をsbtを使うためには、バグを回避する必要があります。Jettyのバグなのか、sbtのバグなのか判りませんが、StackOverFlowにスレが立ってました。
orbitがなんなのか、よくわかりませんが、build.sbtに、こんな感じで書く必要があるようです。
classpathTypes ~= (_ + "orbit") libraryDependencies ++= Seq( "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container" artifacts (Artifact("javax.servlet", "jar", "jar") ) ) libraryDependencies ++= Seq( "org.eclipse.jetty" % "jetty-webapp" % "8.1.5.v20120716" % "container" artifacts (Artifact("jetty-webapp", "jar", "jar")), "org.eclipse.jetty" % "jetty-websocket" % "8.1.5.v20120716" artifacts (Artifact("jetty-websocket", "jar", "jar")) )
また、そもそもsbtから、web-applicationのサポートが分離していて、project/plugins.sbtを追記する必要があった模様。
この説明だと、Jettyのバージョンが古いが、上のとおり、8.1.5でも動く模様。
この設定で、HelloWarldServletが動く。コードは、今回割愛。
Jetty8版のWebSocketServlet
コードは、こんな感じ。エラー追跡のためのログが痛々しいけど、ゆるして。
package com.qtamaki.websocket import scala.collection.mutable.Set import javax.servlet.http._ import org.eclipse.jetty.websocket.WebSocket import org.eclipse.jetty.websocket.WebSocketServlet import org.eclipse.jetty.websocket.WebSocket.Connection import org.eclipse.jetty.websocket.WebSocket.OnTextMessage import org.slf4j.Logger import org.slf4j.LoggerFactory class DraggableServlet extends WebSocketServlet { val logger = LoggerFactory.getLogger(classOf[DraggableServlet]); val clients = Set.empty[DraggableWebSocket] override def doGet(req: HttpServletRequest, resp: HttpServletResponse):Unit = { logger.info(">> doGet") getServletContext.getNamedDispatcher("default").forward(req, resp) } override def doWebSocketConnect(req:HttpServletRequest, protocol:String ):DraggableWebSocket = { logger.info(">> doWebSocketConnect") new DraggableWebSocket } class DraggableWebSocket extends WebSocket.OnTextMessage { var connection:Connection = _ override def onMessage(data:String) = { logger.info(">> onMessage: " + data) clients.foreach{c => try{ c.connection.sendMessage(data)}catch{case e:Exception =>logger.error(e.toString);} } logger.info("<< onMessage") } override def onOpen(connection:Connection) = { logger.info(">> onOpen") this.connection = connection clients += this } override def onClose(closeCode:Int, message:String) = { logger.info(">> onClose") clients -= this } } }
大きな違いは、WebSocketにテキストやバイナリに特化したサブクラスが追加されたこと。今回は、WebSocket.OnTextMessageを使っています。
また、WebSocket.Outboundかなくなり、WebSocket.Connectionになったこと。
WebSocket.OnTextMessageのおかげで、煩雑さが減り、少しすっきりしましたね。Outboundから、Connectionへの変更は、違いがよく判りません。名前が気に入らなかったんでしょうか。
クライアントを書く
そしたら、クライアントを書きます。
今回、独自の面白みを出すために、jQuery ui のDraggableを使って、divをドラッグすると、別のブラウザでも、シンクロして四角が動くという画期的な(?)サンプルを作りました。
だから上のサンプルコードで、DraggableServletだったんですね。:-)
ちなみに蛇足ですが、DraggableServletで普通にChatも動きます(汗)。単純に、テキストで「top,left」を渡していだけですからね。^ ^ ;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>The Synchronized Draggable BOX</title> <link type="text/css" href="css/jquery-ui-1.8.22.custom.css" rel="stylesheet" /> <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="js/jquery-ui-1.8.22.custom.min.js"></script> <style> #draggable { width: 150px; height: 150px; padding: 0.5em; } </style> <script> var ws = new WebSocket("ws://localhost:8080/"); ws.onopen = function(ev) { $('#status').append(" Open"); } ws.onmessage = function(ev) { $('#status').append(" onMessage"); var x = document.getElementById("draggable"); var arr = ev.data.split(","); x.style.top = arr[0]; x.style.left = arr[1]; }; ws.onclose = function(evt) { $('#status').append(" Closed"); }; $(window).unload(function(){ ws.close(); }); $(function() { $( "#draggable" ).draggable({ stop: function(e, ui){ var x = document.getElementById("draggable"); var posi = x.style.top + "," + x.style.left; ws.send(posi); $('#posi').text(posi); } }); }); </script> </head> <body> <h1>The Synchronized Draggable BOX</h1> <p>Status: <span id="status"></span></p> <div class="box"> <div id="draggable" class="ui-widget-content"> <p>Draggable BOX<br/>Posi: <span id=posi></span></p> </div> </div> </body> </html>
試してみると、並べて置いたブラウザで、確かに四角が連動するのでおもしろいです。
今回は、ChromeとFirefoxで試してみました。