読者です 読者をやめる 読者になる 読者になる

セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

マーブルワーズ

Jetty8のWebsocketをScalaから使う

さて。会社の開発合宿にいってきました。(合宿の様子)

それで、僕のテーマは、Websocketにしてみたのですが、色々ハマった上で、おもしろいものができたっぽいので、エントリーしようかと思いました。

「websocket scala」でggrとでてくるid:yuroyoroさんの日記「Jetty7のWebSocketをScalaから使う」、大変参考にさせていただいたのですが、Jetty7を対象にかかれていて、ちょっと古くなっていました。今回これをベースにJetty8で動かすために奮闘したって感じです。

現物

何はさておき、今回作った動くものです。

websocket-jetty-scala

動かし方(多分うごく)

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>

試してみると、並べて置いたブラウザで、確かに四角が連動するのでおもしろいです。
今回は、ChromeFirefoxで試してみました。