Scala初学者がチャットを作るまで 3


とりあえず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との接続なりやってみようかなと思う次第。

コメントする

あなたのメールは 絶対に 公開されたり共有されたりしません。

次の HTML タグと属性が使用できます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>