とりあえずWebSocketとつなごうと必死だった。
が、そもそもjava servletのこともマトモに知らなかったので勉強した。
その記録。
まずは普通のHttpリクエストを受けられるようにして、servletに関して学ぼうと思った。
maven使っているので、とりあえずプロジェクト生成。
mvn archetype:generate
捕捉しておくと、以下の条件でプロジェクトを生成した。
- maven-archetype-webapp
- groupId = com.ymatsu.websample
- artifactId = websample
pom.xmlに以下を追記。※1
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <!-- 中略 -->
  <repositories>
    <repository>
      <id>scala-tools.org</id>
      <name>Scala-tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </repository>
  </repositories>
  
  <pluginRepositories>
    <pluginRepository>
      <id>scala-tools.org</id>
      <name>Scala-tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </pluginRepository>
  </pluginRepositories>
  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>2.7.2</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <!-- jetty -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>7.0.1.v20091125</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlets</artifactId>
      <version>7.0.1.v20091125</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-webapp</artifactId>
      <version>7.0.1.v20091125</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-websocket</artifactId>
      <version>7.0.1.v20091125</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <outputDirectory>src/main/webapp/WEB-INF/classes</outputDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <finalName>websample</finalName>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>
          <webAppConfig>
            <contextPath>/</contextPath>
          </webAppConfig>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
※1 あとで分かるのだが、jettyのバージョンでハマった。
そんでもって、scalaで記述するために必要なディレクトリ生成
mkdir -p src/main/scala/com/ymatsu/websample mkdir -p src/test/scala
コードはこんな感じのものを、src/main/scala/以下に適切なpathで配置。
package com.ymatsu.websample
import javax.servlet.http._
class Demo extends HttpServlet {
   override def doGet(request: HttpServletRequest, response: HttpServletResponse) = response.getWriter().println("Hello World (From Scala!)")
}
あとは、web.xmlをsrc/main/webapp/WEB-INF/に配置。
中身は以下。
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>Home</servlet-name>
    <servlet-class>com.ymatsu.websample.Demo</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Home</servlet-name>
    <url-pattern>/home</url-pattern>
  </servlet-mapping>
</web-app>
mvn jetty:run
でサーバー起動。
http://localhost:8080/home にアクセスしてHello world的な物が表示されればおk!!
んで本題のWebSocket。
とりあえずここのコードを使わせていただいて動かしてみた。
package com.ymatsu.websample
import scala.collection.mutable.Set
import javax.servlet.http._
import org.eclipse.jetty.websocket._
import org.eclipse.jetty.websocket.WebSocket.Outbound
class ChatServlet extends WebSocketServlet {
  val clients = Set.empty[ChatWebSocket]
  override def doGet(req:HttpServletRequest, res:HttpServletResponse ) =
    getServletContext.getNamedDispatcher("Home").forward(req, res)
  override def doWebSocketConnect(req:HttpServletRequest, protocol:String ) =
    new ChatWebSocket
  class ChatWebSocket extends WebSocket {
    var outbound:Outbound = _
    override def onConnect(outbound:Outbound ) = {
      this.outbound = outbound
      clients += this
      onMessage( 0, "WebSocket is success!!!");
    }
    override def onMessage(frame:Byte, data:Array[Byte], offset:Int, length:Int ) = {}
    override def onMessage(frame:Byte, data:String ) =
      clients.foreach{ c => c.outbound.sendMessage( frame, data ) }
    override def onDisconnect = clients -= this
  }
}
そんでもってweb.xmlに適当なパスを追記。(今回はws://localhost:8080/chat)
また、src/main/webapp直下にindex.htmlとして以下のファイルを配置。
<?xml version='1.0' encoding='utf-8' ?>
<html>
  <head>
    <title>websocket test</title>
    <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js' type='text/javascript'></script>
    <script type='text/javascript'>
    var ws = new WebSocket("ws://localhost:8080/socket");
    ws.onmessage = function(e){
    trace(e.data);
    };
    ws.onclose = function(){
    log("ws closed");
    };
    ws.onopen = function(){
    log('connected!!');
    };
    
    $(function(){
    $('input#post').click(function(){
        var name = $('input#name').val();
    var mes = $('input#message').val();
    ws.send(name+" : "+mes);
    $('input#message').val("");
    });
    });
    function log(message){
    trace("[log] "+message);
    };
    function trace(message){
    var mes_div = $('<div />').html(message);
    $('div#chat').prepend(mes_div);
    };
    </script>
  </head>
  <body>
    <form id='message'>
      <input id='name' size='15' type='text' value='名前' />
      <input id='message' size='80' type='text' value='Hello world!!' />
      <input id='post' type='button' value='post' />
    </form>
    <div id='chat'></div>
  </body>
</html>
ここまで来ればあとはmvn jetty:run で動く!
と思っていた。
が!
が!
動かない。
ずっと原因を追っていくと、servletがwebsocketのリクエストをGetとしてしか受け取ってくれない!!!
なぜだ!
と思ったがふとWebSocketの仕様が頭に浮かんで調べてみると、案の定jettyのver7.0は現状のchromeのwebsocket仕様に追いついていなかったらしい。
なのでpom.xmlのjettyのバージョンを以下に書き換え。
各jettyのdependencyに関して
- <version>7.0.1.v20091125</version> + <version>7.5.4.v20111024</version>
pluginに関して
- <artifactId>maven-jetty-plugin</artifactId> + <artifactId>jetty-maven-plugin</artifactId> + <version>7.5.4.v20111024</version>
そんでバージョンに合わせてソースコードを以下のように編集。
package com.ymatsu.websample
import scala.collection.mutable.Set
import javax.servlet.http._
import org.eclipse.jetty.websocket._
import org.eclipse.jetty.websocket.WebSocket.Connection
class App extends WebSocketServlet {
  val clients = Set.empty[ChatWebSocket]
  override def doGet(req:HttpServletRequest, res:HttpServletResponse ) =
    getServletContext.getNamedDispatcher("Home").forward(req, res)
  override def doWebSocketConnect(req:HttpServletRequest, protocol:String ) =
    new ChatWebSocket
  class ChatWebSocket extends WebSocket.OnTextMessage {
    var connection:Connection = _
    override def onOpen(connection:Connection ) = {
      this.connection = connection
      clients += this
      onMessage("WebSocket is success!!!");
    }
    override def onMessage(data:String ) =
      clients.foreach{ c => c.connection.sendMessage(data) }
    override def onClose(code:Int,message:String) = clients -= this
  }
}
以上でやっとこさ動きましたとさ。
チャットとしてはこれでも十分動きますが、チャットルームとか欲しいし、保存したいのでもっと色々勉強。
次はMongoDBとの接続なりやってみようかなと思う次第。
