Jettyで始めるWebSocket超入門

第5回クライアント側の実装

今回は、WebSocket APIについて解説と、WebSocketを使ったチャットアプリケーションのクライアント側の実装を行ないます。

WebSocketオブジェクト

ブラウザのJavaScriptからWebSocketを利用するために、WebSocket APIがW3Cで策定中です。執筆時点のブラウザ(Safari及びChrome)は、最新のドラフトEditor's Draft 27 July 2010に対応していません。ここでは、現行のブラウザが対応しているWebSocketの仕様を解説した後、最新の仕様について説明します。

インスタンス化

WebSocketはインスタンス時、第一引数に「ws://」または「wss://」で始まる文字列で接続先を指定します。また、省略可能な第二引数にはサブプロトコルを指定できます。サブプロトコルについては後述します。

new WebSocket(url)
new WebSocket(url,protocol)

イベント登録

WebSocketは以下の4つのイベントハンドラが定義されています。

onopenWebSocketの接続が完了した時に呼ばれます(イベントタイプ:"open")
oncloseWebSocketが切断された時に呼ばれます(イベントタイプ:"close")
onmessage接続先からメッセージを受け取った時に呼ばれます(イベントタイプ:"message")
onerrorエラー発生した時に呼ばれます(イベントタイプ:"error")

メソッド

定義されているWebSocketのメソッドは以下の2つです。

send(message)messageを接続先に送信します
close()WebSocketの接続を切断します。意図的に切断する時だけでなく、ウィンドウを閉じるとき、画面を遷移する時にも切断処理を行なう必要があります

ステータス

WebSocketの接続状態を調べる事ができます。値こそ違いますが、XHR[1]のreadyStateプロパティと同様のものです。

readyState接続のステータスです。0~2の値をとります。読み取り専用です

WebSocketクラスに定数が準備されています。ステータスを確認する際には必ず定数を使うようにしてください。

CONNECTING接続開始の処理中を示す定数です(= 0)
OPEN接続済を示す定数です(= 1)
CLOSED切断済を表す定数です(= 2)

その他のプロパティ

その他に以下のプロパティがあります。

URL接続先を示すプロパティです。読み取り専用です
bufferedAmountsendメソッドによって送信が指定されてデータは、実際に送信されるまで待ち行列に追加されます。bufferedAmountは待ち行列のバイト数を返します。読み取り専用です

サブプロトコル

サブプロトコルは、開発者がWebSocketプロトコル上で新しいプロトコルを定義する際に使用します。例えば、チャットとファイル転送でWebSocketを使い分けたい時等が考えられます。

Jettyでは、⁠WebSocketServlet」インターフェイスの実装の「doWebSocketConnect」メソッドにサブプロトコル名が渡されます。受け取ったサブプロトコル名により、使用するWebSocketの実装を切り換えることが可能になります。

  protected WebSocket doWebSocketConnect(HttpServletRequest request,
      String protocol) {
    if(protocol=="file"){
      return new FileWebSocket();
    }else{
      return new ChatWebSocket();
    }
  }

今の仕様では、複数のサブプロトコルを同時に使うことは想定されておらず、WebSocketのインスタンス変数を複数準備する必要があるようです。

最新ドラフト

最新のドラフトでは以下のように修正されています。

サブプロトコルの複数指定

コンストラクタがひとつ増えました。第二引数のprotocolsは文字列の配列です。複数のサブプロトコルを複数指定できます。WebSocketプロトコルの仕様にはまだ反映されていないようですが、WebSocket APIの仕様を読む限りでは、接続元が指定した複数のサブプロトコルから、接続先がどれかひとつを選択するようです。

new WebSocket(url,protocols)

ステータス

WebSocketの接続状態を示すreadStateの仕様が変更になりました。

readyState接続のステータスの範囲が0~3に変更されました。読み取り専用です

定数に「CLOSING」が増えています。それに伴い「CLOSED」の値が変更になっています。ドラフトの版の違いによる不具合を避けるためにも、ステータスを確認する際には必ず定数を使うようにしてください。

CONNECTING接続開始の処理中を示す定数です(= 0)
OPEN接続済を示す定数です(= 1)
CLOSING切断中を表す定数です(= 2)
CLOSED切断済を表す定数です(= 3)

プロパティ

protocolサブプロトコル名を返します。インスタンス化の際に、サーバが選択したサブプロトコルを取得するために準備されたと思われます。読み取り専用です
url接続先を示すプロパティが小文字に変更になりました。読み取り専用です

クライアント側の実装

それでは、クライアント側の実装を行ないます。まず、Mavenによって作成された「src/main/resources」内に、⁠html」という名前の新しいディレクトリを作成してください。そして、以下の3つのファイルを作成します。

index.html

チャットアプリケーションのHTMLファイルです。後述する「common.css」「common.js」を読み込んでいます。チャットのメッセージを入力するテキストボックスに「message」というidを、メッセージ送信用のボタンに「send」というidを割り当てています。また、メッセージの表示箇所のidは「messages」となっています。

index.html
<html>
<head>
<title>WebSocketChat</title>
<link type="text/css" href="common.css" rel="stylesheet">
<script src="common.js" type="text/javascript"></script>
</head>
<body>
<div><input type="text" id="message" /><input type="button" id="send" value="send" /></div>
<div id="messages"></div>
</body>
</html>

common.css

チャットアプリケーションの見た目を整えるスタイルシートです。最低限の指定しか行なっていないため、説明は割愛します。

common.css
#message{
  width:400px;
}
#messages{
  width:100%;
  height:400px;
  overflow:hidden;
  border:0px none transparent;
}

common.js

今回の肝となるJavaScriptのコードです[2]⁠。詳細はコメントを参照してください。

common.js
//グローバル変数にWebSocketの変数を定義
var ws;

//getElementByIdの別名を定義
function $(id){
  return document.getElementById(id);
}

//WebSocketが接続された時に、「send」ボタンが有効になる
function onOpenWebSocket(){
  $("send").addEventListener("click",sendMessage,false);
  dispMessage("connected");
}

//WebSocketが切断された時に、「send」ボタンを無効にする
function onCloseWebSocket(){
  $("send").removeEventListener("click",sendMessage,false);
  dispMessage("disconnected");
}

//接続先よりメッセージを受信した時に、空文字でなければ画面に表示する
function onMessageWebSocket(event){
  var msg=event.data;
  if(msg==""){return;}
  dispMessage("> "+msg);
}

//ウィンドウを閉じたり画面遷移した時にWebSokcetを切断する
function onUnload(){
  ws.close();
}

//画面にメッセージを表示する
//上に表示されるメッセージが最新となる
function dispMessage(msg){
  var elem=document.createElement("div");
  elem.appendChild(document.createTextNode(msg));
  if($("messages").hasChildNodes()){
    $("messages").insertBefore(elem,$("messages").firstChild);
  }else{
    $("messages").appendChild(elem);
  }
}

//メッセージ入力欄が空白でなければメッセージを送信する
function sendMessage(){
  var message=$("message").value;
  if(message==""){return;}
  ws.send(message);
  $("message").value="";
}

//初期化処理
function initial(){

  //HTTPSで接続されている場合、WebSocketもセキュアにする
  var protocol=(location.protocol=="https:")?"wss":"ws";

  //port番号も込みで取得
  var host=location.host;

  //接続先URLの組み立て
  var url=protocol+"://"+host+"/ws/";

  //WebSocketのインスタンス化
  ws=new WebSocket(url);

  //WebSocketのイベントの登録
  ws.addEventListener("open",onOpenWebSocket,false);
  ws.addEventListener("close",onCloseWebSocket,false);
  ws.addEventListener("message",onMessageWebSocket,false);

  //ウィンドウを閉じたり画面遷移した時にWebSokcetを切断する
  window.addEventListener("unload",onUnload,false);

}

//オンロード時のイベントに、初期化関数を定義
window.addEventListener("load",initial,false);

チャットサーバの起動

これでWebSocketを使ったチャットの実装は一通り完成しました。それでは、サーバを起動してみましょう。

サーバを起動する前に、コンパイルをする必要があります。プロジェクト・エクスプローラー内の「WebSocketChat」プロジェクトを右クリックしコンテキストメニューを表示し、⁠実行⁠⁠→⁠Maven package」を選択するとコンパイルが始まります[3]⁠。コンソールに「BUILD SUCCESSFUL」と表示されれば、コンパイルの完了です。

その後、プロジェクト・エクスプローラー内の「WebSocketChat.java」ファイルを右クリックし、⁠実行⁠⁠→⁠Java アプリケーション」を選択し、サーバを起動してください。

コンソールに「Started SelectChannelConnector@0.0.0.0:8040」と表示されたら、WebSocketに対応したブラウザのウィンドウを複数開き、それぞれ「http://localhost:8040/」にアクセスしてください。テキストボックスにメッセージを入力し、⁠send」ボタンをクリックすると、すべてのウィンドウにメッセージが表示されます。

図1 メッセージ送信側の画面
図1 メッセージ送信側の画面
図2 メッセージ受信側の画面
図2 メッセージ受信側の画面

次回予告

次回は、チャットをアプリケーション化し、誰でも簡単に起動できるようにします。

おすすめ記事

記事・ニュース一覧