// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= WebアプリケーションでのWebSocket APIの使用
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: WebアプリケーションでのWebSocket APIの使用 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, WebアプリケーションでのWebSocket APIの使用

このチュートリアルでは、1つのサーバー・アプリケーションに接続されているクライアント・ブラウザの間のコラボレーションを可能にする単純なWebアプリケーションを作成する方法を示します。ユーザーがクライアント・ブラウザでキャンバスにグラフィック要素を描画すると、接続されているすべてのクライアントのキャンバスにその要素が表示されます。使用方法ブラウザがWebページをロードすると、クライアント側のスクリプトはWebSocketハンドシェイク・リクエストをアプリケーション・サーバーに送信します。アプリケーションは、セッションで接続されているクライアントからJSONおよびバイナリ・メッセージを受け付けて、接続されているすべてのクライアントにメッセージをブロードキャストできます。

このチュートリアルでは、ブラウザ・クライアントとアプリケーション・サーバーとの間の双方向の通信を可能にするJava API for WebSocket (link:http://www.jcp.org/en/jsr/detail?id=356[+JSR 356+])を使用してWebアプリケーションを作成します。Java API for WebSocketでは、WebSocket Javaコンポーネントの作成、WebSocketイベントの開始とインターセプト、WebSocketのテキストおよびバイナリのメッセージの作成と消費のサポートが提供されます。チュートリアルでは、Java API for JSON Processing (link:http://jcp.org/en/jsr/detail?id=353[+JSR 353+])を使用してJSONを生成および消費する方法も示します。Java API for WebSocketおよびJava API for JSON ProcessingはJava EE 7プラットフォーム(link:http://jcp.org/en/jsr/detail?id=342[+JSR 342+])の一部です。

アプリケーションには、WebSocketエンドポイント、デコーダおよびエンコーダのインタフェース、Webページ、およびページのロード時またはWebページのフォームからの起動時にクライアント・ブラウザで実行されるJavaScriptファイルが含まれます。Java EE 7テクノロジのリファレンス実装であるGlassFish Server Open Source Edition 4にアプリケーションをデプロイします。

NOTE: このチュートリアルは、link:http://blog.arungupta.me/[+Arun Gupta氏のブログ+]で見ることができるブログ投稿link:https://blogs.oracle.com/arungupta/entry/collaborative_whiteboard_using_websocket_in[+ Collaborative Whiteboard using WebSocket in GlassFish 4 - Text/JSON and Binary/ArrayBuffer Data Transfer (TOTD #189)+]およびその他のブログ・エントリに基づいています。このブログにアクセスして、WebSocket APIやGlassFish 4の使用に関するその他の多くの有用なエントリをぜひ参照してください。

link:maven-websocketapi-screencast.html[+「WebアプリケーションでのWebSocket APIの使用のビデオ」+]も参照できます。

*チュートリアルの課題*

* <<Exercise_1,Webアプリケーション・プロジェクトの作成>>
* <<createendpoint,WebSocketエンドポイントの作成>>
* <<createendpoint1,エンドポイントの作成>>
* <<createendpoint2,WebSocketセッションの開始>>
* <<createendpoint3,エンドポイントのテスト>>
* <<createwhiteboard,ホワイトボードの作成>>
* <<createwhiteboard1,キャンバスの追加>>
* <<createwhiteboard2,POJOの作成>>
* <<createwhiteboard3,座標クラスの作成>>
* <<createwhiteboard6,JSON文字列の生成>>
* <<createwhiteboard4,エンコーダおよびデコーダ・インタフェースの実装>>
* <<createwhiteboard5,アプリケーションの実行>>
* <<sendbinary,エンドポイントへのバイナリ・データの送信>>

*このチュートリアルに従うには、次のソフトウェアとリソースが必要です。*

|===
|ソフトウェアまたはリソース |必須バージョン 

|link:https://netbeans.org/downloads/index.html[+NetBeans IDE+] |7.3.1、7.4、8.0、Java EEバージョン 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java Development Kit (JDK)+] |バージョン7または8 

|link:https://glassfish.java.net/[+GlassFish Server Open Source Edition+] |4 
|===

NOTE: GlassFish 4は、NetBeans IDEのJava EEダウンロード・バンドルにバンドルされています。

*前提条件*

このドキュメントは、次のテクノロジについて基本的な知識またはプログラミング経験を持つ読者を想定して書かれています。

* Javaプログラミング
* JavaScript/HTMLプログラミング
* NetBeans IDE

このチュートリアルを開始する前に、必要に応じて次のドキュメントをお読みください。

* link:http://wiki.netbeans.org/MavenBestPractices[+NetBeans IDEでのApache Mavenのベスト・プラクティス+]
* link:http://books.sonatype.com/mvnref-book/reference/introduction.html[+Chapter 1. Introducing Apache Maven+] (link:http://books.sonatype.com/mvnref-book/reference/index.html[+Maven: The Complete Reference +])

link:https://netbeans.org/projects/samples/downloads/download/Samples/JavaEE/WhiteboardApp.zip[+終了したプロジェクトのZIPアーカイブ+]はダウンロードできます。


== Webアプリケーション・プロジェクトの作成

この課題の目標は、IDEで新規プロジェクト・ウィザードを使用してWebアプリケーション・プロジェクトを作成することです。プロジェクトを作成する際、Java EEバージョンとして「Java EE 7」を、アプリケーション・サーバーとしてGlassFish 4を選択します。GlassFish 4はJava EE 7プラットフォームのリファレンス実装です。このチュートリアルでアプリケーションを作成するには、IDEに登録されたJava EE 7をサポートするアプリケーション・サーバーが必要です。

1. メイン・メニューから「ファイル」>「新規プロジェクト」(Windowsの場合は[Ctrl]-[Shift]-[N]、Macの場合は[⌘]-[Shift]-[N])を選択します。
2. 「Maven」カテゴリから「Webアプリケーション」を選択します。「次」をクリックします。
3. 「プロジェクト名」に*「WhiteboardApp」*と入力し、プロジェクトの場所を設定します。
4. 「グループID」に*「org.sample」*と入力します。「次」をクリックします。
5. 「サーバー」に*「GlassFish Server 4.0」*を選択します。
6. 「Java EEバージョン」を*「Java EE 7 Web」*に設定します。「終了」をクリックします。

image::images/websocket-newproject.png[title="新規プロジェクト・ウィザードの「サーバー」および「Java EEバージョン」"]

「終了」をクリックすると、IDEがプロジェクトを作成し、そのプロジェクトが「プロジェクト」ウィンドウで開きます。


== WebSocketエンドポイントの作成

この項では、WebSocketエンドポイント・クラスおよびJavaScriptファイルを作成します。WebSocketエンドポイント・クラスには、セッションのオープン時に実行される基本的なメソッドを含めます。次に、ページのロード時にサーバーとのハンドシェイクを開始するJavaScriptファイルを作成します。次に、接続が正常であることをテストするアプリケーションを実行します。

WebSocket APIおよび注釈の使用の詳細は、link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/package-summary.html[+ javax.websocket+]パッケージのサマリーを参照してください。


=== エンドポイントの作成

この課題では、IDEのウィザードを利用してWebSocketエンドポイント・クラスを作成します。

1. 「プロジェクト」ウィンドウで「ソース・パッケージ」ノードを右クリックし、「新規」>「その他」を選択します。
2. 「Web」カテゴリで「WebSocketエンドポイント」を選択します。「次」をクリックします。
3. 「クラス名」に*「MyWhiteboard」*と入力します。
4. 「パッケージ」ドロップダウン・リストで「 ``org.sample.whiteboardapp`` 」を選択します。
5. 「WebSocket URI」に*「/whiteboardendpoint」*と入力します。「終了」をクリックします。

image::images/websocket-newendpoint.png[title="新規ファイル・ウィザードのWebSocketエンドポイント"]

「終了」をクリックすると、IDEによってWebSocketエンドポイント・クラスが生成され、ソース・エディタでファイルが開きます。エディタで、IDEによってWebSocket APIの一部である注釈が生成されたことを確認できます。クラスには、クラスがエンドポイントであることを識別する ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/server/ServerEndpoint.html[+@ServerEndpoint+]`` という注釈が付けられ、注釈のパラメータとしてWebSocket URIが指定されています。IDEによって ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnMessage.html[+@OnMessage+]`` という注釈が付けられたデフォルトの ``onMessage`` メソッドも生成されました。 ``@OnMessage`` という注釈が付けられたメソッドは、クライアントがWebSocketメッセージを受信するたびに起動されます。


[source,java]
----

@ServerEndpoint("/whiteboardendpoint")
public class MyWhiteboard {

    @OnMessage
    public String onMessage(String message) {
        return null;
    }
    
}
----

[start=6]
. 次のフィールド(*太字*部分)をクラスに追加します。

[source,java]
----

@ServerEndpoint("/whiteboardendpoint")
public class MyWhiteboard {
    *private static Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());*

    @OnMessage
    public String onMessage(String message) {
        return null;
    }
}
----

[start=7]
. 次の ``onOpen`` および ``onClose`` メソッドを追加します。

[source,java]
----

    @OnOpen
    public void onOpen (Session peer) {
        peers.add(peer);
    }

    @OnClose
    public void onClose (Session peer) {
        peers.remove(peer);
    }
----

 ``onOpen`` および ``onClose`` メソッドには、 ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnOpen.html[+@OnOpen+]`` および ``link:https://javaee-spec.java.net/nonav/javadocs/javax/websocket/OnClose.html[+@OnClose+]`` のWebSocket API注釈が付けられています。 ``@OnOpen`` という注釈が付けられたメソッドは、Webソケット・セッションが開かれたときにコールされます。この例では、注釈の付いた ``onOpen`` メソッドでブラウザ・クライアントを現在のセッションのピアのグループに追加し、 ``onClose`` メソッドでブラウザをグループから削除します。

メソッドの生成には、ソース・エディタのヒントとコード補完を使用すると便利です。クラスの宣言の横の左マージンのヒント・グリフをクリックし(または、カーソルをクラスの宣言内に置いて[Alt]-[Enter])、ポップアップ・メニューでメソッドを選択します。コード補完をメソッドのコーディングに使用すると便利です。

image::images/websocket-endpoint-hint.png[title="ソース・エディタのコード・ヒント"]


[start=8]
. エディタで右クリックし、「インポートを修正」を選択します([Alt]-[Shift]-[I]、Macの場合は[⌘]-[Shift]-[I])。変更を保存します。

 ``javax.websocket`` のクラスのインポート文がファイルに追加されます。

これでエンドポイントが作成されました。次にWebSocketセッションを開始するためのJavaScriptファイルを作成する必要があります。

 


=== WebSocketセッションの開始

この課題では、WebSocketセッションを開始するJavaScriptファイルを作成します。ブラウザ・クライアントは、サーバーとのHTTPハンドシェイクを使用し、TCPを介してセッションに参加します。JavaScriptファイルで、エンドポイントの ``wsURI`` の名前を指定し、WebSocketを宣言します。 ``wsURI``  URIスキームはWebSocketプロトコルの一部で、アプリケーションのエンドポイントのパスを指定します。

1. 「プロジェクト」ウィンドウでプロジェクト・ノードを右クリックし、「新規」>「その他」を選択します。
2. 新規ファイル・ウィザードの「Web」カテゴリで「JavaScriptファイル」を選択します。「次」をクリックします。
3. 「JavaScriptファイル名」に*「websocket」*と入力します。「終了」をクリックします。
4. 次のコードをJavaScriptファイルに追加します。

[source,javascript]
----

var wsUri = "ws://" + document.location.host + document.location.pathname + "whiteboardendpoint";
var websocket = new WebSocket(wsUri);

websocket.onerror = function(evt) { onError(evt) };

function onError(evt) {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
----

このスクリプトは、ブラウザによって ``websocket.js`` がロードされる際、サーバーとセッション・ハンドシェイクを開始します。


[start=5]
.  ``index.html`` を開き、ページのロードの終了時に ``websocket.js`` をロードする次のコード(*太字*部分)をファイルの最後に追加します。

[source,html]
----

<body>
    *<h1>Collaborative Whiteboard App</h1>
        
    <script type="text/javascript" src="websocket.js"></script>*
</body>
----

これで、WebSocketエンドポイントが機能していること、セッションが開始されたこと、およびクライアントがセッションに追加されたことをテストできます。

 


=== エンドポイントのテスト

この課題では、ブラウザがエンドポイントに接続されたら、ブラウザ・ウィンドウに ``wsURI`` を出力するよう、簡単なメソッドをいくつかJavaScriptに追加します。


[start=1]
. 次の ``<div>`` タグ(*太字*部分)を ``index.html`` に追加します。

[source,html]
----

<h1>Collaborative Whiteboard App</h1>
        
*<div id="output"></div>*
<script type="text/javascript" src="websocket.js"></script>
----

[start=2]
. 次の宣言とメソッドを ``websocket.js`` に追加します。変更を保存します。

[source,javascript]
----

// For testing purposes
var output = document.getElementById("output");
websocket.onopen = function(evt) { onOpen(evt) };

function writeToScreen(message) {
    output.innerHTML += message + "<br>";
}

function onOpen() {
    writeToScreen("Connected to " + wsUri);
}
// End test functions
----

ページがロードされると、JavaScript関数は、ブラウザがエンドポイントに接続されていることを示すメッセージを出力します。エンドポイントが正しく実行されていることを確認したら、関数を削除できます。


[start=3]
. 「プロジェクト」ウィンドウでプロジェクトを右クリックし、「実行」を選択します。

アプリケーションを実行すると、IDEでGlassFishサーバーが起動され、アプリケーションがビルドおよびデプロイされます。ブラウザでindexページが開かれ、ブラウザ・ウィンドウに次のメッセージが表示されます。

image::images/websocket-browser1.png[title="ブラウザ・ウィンドウ内のエンドポイントへの接続メッセージ"]

ブラウザ・ウィンドウに、メッセージが受け付けられたエンドポイント( ``http://localhost:8080/WhiteboardApp/whiteboardendpoint`` )が表示されます。


== ホワイトボードの作成

この項では、JSONテキスト・メッセージを送受信するクラスおよびJavaScriptファイルを作成します。コンテンツ、およびペイントブラシの形状と色を指定するラジオ・ボタンを含むHTML ``<form>`` を描画および表示するためのlink:http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html[+HTML5 Canvas+]要素も追加します。


=== Webページへのキャンバスの追加

この課題では、 ``canvas`` 要素および ``form`` 要素をデフォルトのindexページに追加します。フォームのチェックボックスによって、キャンバスのペイントブラシのプロパティが決まります。

1. ソース・エディタで ``index.html`` を開きます。
2. エンドポイントのテスト用に追加した ``<div>`` タグを削除し、開始のbodyタグの後に次の ``<table>`` および ``<form>`` 要素(*太字*部分)を追加します。

[source,html]
----

<h1>Collaborative Whiteboard App</h1>
        
    *<table>
        <tr>
            <td>
            </td>
            <td>
                <form name="inputForm">
                    

                </form>
            </td>
        </tr>
    </table>*
    <script type="text/javascript" src="websocket.js"></script>
    </body>
----

[start=3]
. canvas要素用に次のコード(*太字*部分)を追加します。

[source,html]
----

        <table>
            <tr>
                <td>
                    *<canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>*
                </td>
----

[start=4]
. 次の ``<table>`` を追加して、色と形状を選択するラジオ・ボタンを追加します。変更を保存します。

[source,html]
----

        <table>
            <tr>
                <td>
                    <canvas id="myCanvas" width="150" height="150" style="border:1px solid #000000;"></canvas>
                </td>
                <td>
                    <form name="inputForm">
                        *<table>

                            <tr>
                                <th>Color</th>
                                <td><input type="radio" name="color" value="#FF0000" checked="true">Red</td>
                                <td><input type="radio" name="color" value="#0000FF">Blue</td>
                                <td><input type="radio" name="color" value="#FF9900">Orange</td>
                                <td><input type="radio" name="color" value="#33CC33">Green</td>
                            </tr>

                            <tr>
                                <th>Shape</th>
                                <td><input type="radio" name="shape" value="square" checked="true">Square</td>
                                <td><input type="radio" name="shape" value="circle">Circle</td>
                                <td> </td>
                                <td> </td>
                            </tr>

                        </table>*
                    </form>
----

キャンバス上に描画された図形の形状、色、および座標は、JSONの構造の文字列に変換され、WebSocketエンドポイントにメッセージとして送信されます。

 


=== POJOの作成

この課題では、単純なPOJOを作成します。

1. プロジェクト・ノードを右クリックし、「新規」>「Javaクラス」を選択します。
2. 「クラス名」に*「Figure」*と入力し、「パッケージ」ドロップダウン・リストで「 ``org.sample.whiteboardapp`` 」を選択します。「終了」をクリックします。
3. ソース・エディタで、次のコード(*太字*部分)を追加します。

[source,java]
----

public class Figure {
    *private JsonObject json;*
}
----

コードを追加すると、 ``javax.json.JsonObject`` のインポート文を追加するよう求められます。求められない場合は、[Alt]-[Enter]を押します。

 ``javax.json.JsonObject`` の詳細は、Java EE 7仕様の一部であるJava API for JSON Processing (link:http://jcp.org/en/jsr/detail?id=353[+JSR 353+])を参照してください。


[start=4]
.  ``json`` の取得および設定メソッドを作成します。

「コードを挿入」ポップアップ・メニュー(Windowsの場合は[Alt]-[Insert]、Macの場合は[Ctrl]-[I])で取得および設定メソッドを選択すると、「取得メソッドおよび設定メソッドの生成」ダイアログ・ボックスが開きます。または、メイン・メニューから「ソース」>「コードを挿入」を選択します。

image::images/websocket-generategetter.png[title="「取得メソッドおよび設定メソッドの生成」ダイアログ・ボックス"]


[start=5]
.  ``json`` のコンストラクタを追加します。

[source,java]
----

    public Figure(JsonObject json) {
        this.json = json;
    }
----

「コードを挿入」ポップアップ・メニュー([Ctrl]-[I])で「コンストラクタ」を選択します。

image::images/websocket-generateconstructor.png[title="「コンストラクタの生成」ポップアップ・メニュー"]


[start=6]
. 次の ``toString`` メソッドを追加します。

[source,java]
----

    @Override
    public String toString() {
        StringWriter writer = new StringWriter();
        Json.createWriter(writer).write(json);
        return writer.toString();
    }
----

[start=7]
. エディタで右クリックし、「インポートを修正」を選択します([Alt]-[Shift]-[I]、Macの場合は[⌘]-[Shift]-[I])。変更を保存します。
 


=== 座標クラスの作成

ここで、キャンバスに描画される図形の座標のクラスを作成します。

1. プロジェクト・ノードを右クリックし、「新規」>「Javaクラス」を選択します。
2. 新規Javaクラス・ウィザードで「クラス名」に*「Coordinates」*と入力し、「パッケージ」ドロップダウン・リストで「 ``org.sample.whiteboardapp`` 」を選択します。「終了」をクリックします。
3. ソース・エディタで、次のコードを追加します。変更を保存します。

[source,java]
----

    private float x;
    private float y;

    public Coordinates() {
    }

    public Coordinates(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
                
----

クラスには ``x`` と ``y`` 座標のフィールドおよび取得と設定のメソッドのみが含まれます。

 


=== JSON文字列の生成

この課題では、 ``canvas`` 要素に描画される図形の詳細を、websocketエンドポイントに送信されるJSON構造にするJavaScriptファイルを作成します。


[start=1]
. プロジェクト・ノードを右クリックし、「新規」>「JavaScriptファイル」を選択して新規JavaScriptファイル・ウィザードを開きます。

[start=2]
. 「ファイル名」に*「whiteboard」*と入力します。「終了」をクリックします。

「終了」をクリックすると、IDEで空のJavaScriptファイルが作成され、エディタでこのファイルが開きます。「プロジェクト」ウィンドウの「Webページ」ノードの下に新規ファイルが表示されます。


[start=3]
. キャンバスを初期化し、イベント・リスナーを追加する次のコードを追加します。

[source,javascript]
----

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
canvas.addEventListener("click", defineImage, false);
----

ユーザーが ``canvas`` 要素内をクリックすると、 ``defineImage`` メソッドが起動されることがわかります。


[start=4]
. 次の ``getCurrentPos`` 、 ``defineImage`` および ``drawImageText`` メソッドを追加して、JSON構造を作成し、エンドポイントに送信します( ``sendText(json)`` )。

[source,javascript]
----

function getCurrentPos(evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: evt.clientX - rect.left,
        y: evt.clientY - rect.top
    };
}
            
function defineImage(evt) {
    var currentPos = getCurrentPos(evt);
    
    for (i = 0; i < document.inputForm.color.length; i++) {
        if (document.inputForm.color[i].checked) {
            var color = document.inputForm.color[i];
            break;
        }
    }
            
    for (i = 0; i < document.inputForm.shape.length; i++) {
        if (document.inputForm.shape[i].checked) {
            var shape = document.inputForm.shape[i];
            break;
        }
    }
    
    var json = JSON.stringify({
        "shape": shape.value,
        "color": color.value,
        "coords": {
            "x": currentPos.x,
            "y": currentPos.y
        }
    });
    drawImageText(json);
        sendText(json);
}

function drawImageText(image) {
    console.log("drawImageText");
    var json = JSON.parse(image);
    context.fillStyle = json.color;
    switch (json.shape) {
    case "circle":
        context.beginPath();
        context.arc(json.coords.x, json.coords.y, 5, 0, 2 * Math.PI, false);
        context.fill();
        break;
    case "square":
    default:
        context.fillRect(json.coords.x, json.coords.y, 10, 10);
        break;
    }
}
----

送信されるJSONの構造は次のようになります。


[source,javascript]
----

{
 "shape": "square",
 "color": "#FF0000",
 "coords": {
 "x": 31.59999942779541,
 "y": 49.91999053955078
 }
} 
----

 ``websocket.send()`` を使用してJSON文字列を送信する ``sendText(json)`` メソッドを追加する必要があります。


[start=5]
. エディタで ``websocket.js`` を開き、JSONをエンドポイントに送信するためのメソッドおよびエンドポイントからメッセージを受信したらイメージを描画するためのメソッドを追加します。

[source,javascript]
----

websocket.onmessage = function(evt) { onMessage(evt) };

function sendText(json) {
    console.log("sending text: " + json);
    websocket.send(json);
}
                
function onMessage(evt) {
    console.log("received: " + evt.data);
    drawImageText(evt.data);
}
----

NOTE: エンドポイントのテスト用に ``websocket.js`` に追加したコードは削除できます。


[start=6]
.  ``whiteboard.js`` をロードする次の行(*太字*部分)を ``index.html`` の最後に追加します。

[source,html]
----

        </table>
    <script type="text/javascript" src="websocket.js"></script>
    *<script type="text/javascript" src="whiteboard.js"></script>*
<body>
                
----
 


=== エンコーダおよびデコーダ・インタフェースの実装

この課題では、デコーダおよびエンコーダ・インタフェースを実装するクラスを作成し、Webソケット・メッセージ(JSON)をPOJOクラス ``Figure`` にデコードし、エンドポイントに送信するために ``Figure`` をJSON文字列としてエンコードします。

詳細は、技術記事link:http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html[+JSR 356、Java API for WebSocket+]のメッセージ・タイプおよびエンコーダ、デコーダに関する項を参照してください。

1. プロジェクト・ノードを右クリックし、「新規」>「Javaクラス」を選択します。
2. 「クラス名」に*「FigureEncoder」*と入力し、「パッケージ」ドロップダウン・リストで「 ``org.sample.whiteboardapp`` 」を選択します。「終了」をクリックします。
3. ソース・エディタで次のコード(*太字*部分)を追加し、WebSocket Encoderインタフェースを実装します。

[source,java]
----

            
public class FigureEncoder *implements Encoder.Text<Figure>* {
    
}
----

[start=4]
.  ``javax.websocket.Encoder`` のインポート文を追加し、抽象メソッドを実装します。

クラスの宣言にカーソルを置き、[Alt]-[Enter]を押して、ポップアップ・メニューから*「すべての抽象メソッドを実装」*を選択します。


[start=5]
. 次の変更(*太字*部分)を加えて、生成された抽象メソッドを変更します。変更を保存します。

[source,java]
----

    @Override
    public String encode(Figure *figure*) throws EncodeException {
        *return figure.getJson().toString();*
    }

    @Override
    public void init(EndpointConfig ec) {
        *System.out.println("init");*
    }

    @Override
    public void destroy() {
        *System.out.println("destroy");*
    }
----

[start=6]
. プロジェクト・ノードを右クリックし、「新規」>「Javaクラス」を選択します。

[start=7]
. 「クラス名」に*「FigureDecoder」*と入力し、「パッケージ」ドロップダウン・リストで「 ``org.sample.whiteboardapp`` 」を選択します。「終了」をクリックします。

[start=8]
. ソース・エディタで、次のコード(*太字*部分)を追加し、WebSocket Decoderインタフェースを実装します。

[source,java]
----

            
public class FigureDecoder *implements Decoder.Text<Figure>* {
    
}
----

[start=9]
.  ``javax.websocket.Decoder`` のインポート文を追加し、抽象メソッドを実装します。

[start=10]
. 生成された抽象メソッドに次の変更(*太字*部分)を加えます。

[source,java]
----

    @Override
    public Figure decode(String *string*) throws DecodeException {
        *JsonObject jsonObject = Json.createReader(new StringReader(string)).readObject();
        return  new Figure(jsonObject);*
    }

    @Override
    public boolean willDecode(String *string*) {
        *try {
            Json.createReader(new StringReader(string)).readObject();
            return true;
        } catch (JsonException ex) {
            ex.printStackTrace();
            return false;
        }*
    
    }

    @Override
    public void init(EndpointConfig ec) {
        *System.out.println("init");*
    }

    @Override
    public void destroy() {
        *System.out.println("destroy");*
    }
----

[start=11]
. インポートを修正して変更内容を保存します。

次に、 ``MyWhiteboard.java`` を変更して、エンコーダとデコーダを指定する必要があります。

 


=== アプリケーションの実行

これでアプリケーションを実行する準備がほぼ整いました。この課題では、WebSocketエンドポイント・クラスを変更してJSON文字列のエンコーダとデコーダを指定し、メッセージを受信したら、接続されているクライアントにJSON文字列を送信するメソッドを追加します。

1. エディタで ``MyWhiteboard.java`` を開きます。
2.  ``@ServerEndpoint`` 注釈を変更し、エンドポイントのエンコーダとデコーダを指定します。エンドポイントの名前のパラメータ ``value`` を明示的に指定する必要があります。

[source,java]
----

@ServerEndpoint(*value=*"/whiteboardendpoint"*, encoders = {FigureEncoder.class}, decoders = {FigureDecoder.class}*)
        
----

[start=3]
. デフォルトで生成された ``onMessage`` メソッドを削除します。

[start=4]
. 次の ``broadcastFigure`` メソッドを追加し、メソッドに ``@OnMessage`` の注釈を付けます。

[source,java]
----

    @OnMessage
    public void broadcastFigure(Figure figure, Session session) throws IOException, EncodeException {
        System.out.println("broadcastFigure: " + figure);
        for (Session peer : peers) {
            if (!peer.equals(session)) {
                peer.getBasicRemote().sendObject(figure);
            }
        }
    }
----

[start=5]
. エディタで右クリックし、「インポートを修正」を選択します([Alt]-[Shift]-[I]、Macの場合は[⌘]-[Shift]-[I])。変更を保存します。

[start=6]
. 「プロジェクト」ウィンドウでプロジェクトを右クリックし、「実行」を選択します。

「実行」をクリックすると、IDEはブラウザ・ウィンドウでlink:http://localhost:8080/WhiteboardApp/[+http://localhost:8080/WhiteboardApp/+]を開きます。

NOTE: 以前のアプリケーションをアプリケーション・サーバーからアンデプロイするか、ブラウザでページを強制的に再ロードする必要がある場合があります。

ブラウザ・メッセージを確認すると、キャンバスをクリックするたびに、文字列がJSONを介してエンドポイントに送信されていることがわかります。

image::images/websocket-onebrowser.png[title="ブラウザ内の図形を含むキャンバスおよびWebコンソールに表示されたJSON"]

別のブラウザで ``http://localhost:8080/WhiteboardApp/`` を開くと、一方のブラウザのキャンバス内をクリックするたびに新しい円や正方形が他方のブラウザのキャンバスに複製されることがわかります。

image::images/websocket-twobrowsers.png[title="エンドポイントを介してJSONを送信する2つのブラウザ"]


== エンドポイントへのバイナリ・データの送信

これで、アプリケーションで文字列を処理し、JSONを介してエンドポイントに送信できます。文字列は、接続されているクライアントに送信されます。この項では、バイナリ・データを送受信するようJavaScriptファイルを変更します。

バイナリ・データをエンドポイントに送信するために、WebSocketの ``binaryType`` プロパティを ``arraybuffer`` に設定する必要があります。これによって、WebSocketを使用したバイナリ転送は ``ArrayBuffer`` を介して行われることが保証されます。バイナリ・データ変換は、 ``whiteboard.js`` の ``defineImageBinary`` メソッドによって行われます。

1.  ``websocket.js`` を開き、WebSocketの ``binaryType`` プロパティを ``arraybuffer`` に設定する次のコードを追加します。

[source,javascript]
----

websocket.binaryType = "arraybuffer";
----

[start=2]
. バイナリ・データをエンドポイントに送信する次のメソッドを追加します。

[source,javascript]
----

function sendBinary(bytes) {
    console.log("sending binary: " + Object.prototype.toString.call(bytes));
    websocket.send(bytes);
}
----

[start=3]
.  ``onMessage`` メソッドを変更し、受信メッセージのデータ型に応じてキャンバスを更新するメソッドを選択する次のコード(*太字*部分)を追加します。

[source,javascript]
----

function onMessage(evt) {
    console.log("received: " + evt.data);
    *if (typeof evt.data == "string") {*
        drawImageText(evt.data);
    *} else {
        drawImageBinary(evt.data);
    }*
}
----

バイナリ・データのメッセージを受信すると、 ``drawImageBinary`` メソッドが起動されます。


[start=4]
.  ``whiteboard.js`` を開いて、次のメソッドを追加します。受信バイナリ・データの解析後、 ``drawImageBinary`` メソッドを起動してキャンバスを更新します。 ``defineImageBinary`` メソッドを使用して、バイナリ・データとしてキャンバスのスナップショットを準備します。

[source,javascript]
----

function drawImageBinary(blob) {
    var bytes = new Uint8Array(blob);
//    console.log('drawImageBinary (bytes.length): ' + bytes.length);
    
    var imageData = context.createImageData(canvas.width, canvas.height);
    
    for (var i=8; i<imageData.data.length; i++) {
        imageData.data[i] = bytes[i];
    }
    context.putImageData(imageData, 0, 0);
    
    var img = document.createElement('img');
    img.height = canvas.height;
    img.width = canvas.width;
    img.src = canvas.toDataURL();
}
                    
function defineImageBinary() {
    var image = context.getImageData(0, 0, canvas.width, canvas.height);
    var buffer = new ArrayBuffer(image.data.length);
    var bytes = new Uint8Array(buffer);
    for (var i=0; i<bytes.length; i++) {
        bytes[i] = image.data[i];
    }
    sendBinary(buffer);
}
----

バイナリ・データを ``ArrayBuffer`` 型として生成し、エンドポイントに送信する場合に ``defineImageBinary`` を起動する方法を追加する必要があります。


[start=5]
.  ``index.html`` を開き、 ``<table>`` 要素を変更して、フォームの表に次の行を追加します。

[source,html]
----

<tr>
    <th> </th>
    <td><input type="submit" value="Send Snapshot" onclick="defineImageBinary(); return false;"></td>
    <td> </td>
    <td> </td>
    <td> </td>
</tr>
                
----

新しい行には、接続されているピアにキャンバスのバイナリ・スナップショットを送信する「Send Snapshot」ボタンが含まれます。ボタンがクリックされると、 ``whiteboard.js`` の ``defineImageBinary`` メソッドが起動されます。


[start=6]
.  ``MyWhiteboard.java`` を開き、エンドポイントがバイナリ・データのメッセージを受信すると、ピアにバイナリ・データを送信する次のメソッドを追加します。

[source,java]
----

@OnMessage
public void broadcastSnapshot(ByteBuffer data, Session session) throws IOException {
    System.out.println("broadcastBinary: " + data);
    for (Session peer : peers) {
        if (!peer.equals(session)) {
            peer.getBasicRemote().sendBinary(data);
        }
    }
}
----

NOTE:  ``java.nio.ByteBuffer`` のインポート文を追加する必要があります。

アプリケーションを変更して、ユーザーがエンドポイントへのデータの送信を停止できるようにできます。デフォルトでは、すべてのピアはページを開くとすぐに接続され、データはブラウザからすべての接続されているピアに送信されます。オプションが選択されていない場合にデータがエンドポイントに送信されないよう単純な条件文を追加できます。これは、受信するデータに影響しません。データは引き続き、エンドポイントから受信されます。

1.  ``whiteboard.js`` の ``defineImage`` メソッドを変更し、次のコード(*太字*部分)を追加します。

[source,javascript]
----

        drawImageText(json);
*    if (document.getElementById("instant").checked) {*
        sendText(json);
*    }*
}
----

id  ``checked`` の要素をチェックする条件コード


[start=2]
.  ``index.html`` を開き、 ``<table>`` 要素を変更してフォームにチェックボックスを追加します。

[source,html]
----

<tr>
    <th> </th>
    <td><input type="submit" value="Send Snapshot" onclick="defineImageBinary(); return false;"></td>
    <td>*<input type="checkbox" id="instant" value="Online" checked="true">Online*</td>
    <td> </td>
    <td> </td>
</tr>
                
----

「Online」チェックボックスが選択解除されている場合、データは送信されませんが、クライアントは引き続きエンドポイントからデータを受信します。

「Send Snapshot」ボタンおよび「Online」チェックボックスを追加してアプリケーションを再度実行すると、indexページに新しい要素が表示されます。別のブラウザを開いて「Online」ボタンを選択解除すると、キャンバス内をクリックしたときにJSONメッセージが送信されないことがわかります。

image::images/websocket-onebrowser-binary.png[title="バイナリ・データが送信されたというメッセージを表示するブラウザ内のWebコンソール"]

「Send Snapshot」をクリックすると、バイナリ・データがエンドポイントに送信され、接続されているクライアントにブロードキャストされます。


link:/about/contact_form.html?to=3&subject=Feedback:%20Using%20the%20WebSocket%20API%20in%20a%20Web%20Application[+このチュートリアルに関するご意見をお寄せください+]



== 関連項目

NetBeans IDEを使用したJava EEアプリケーションの開発方法の詳細は、次のリソースを参照してください。

* デモ: link:maven-websocketapi-screencast.html[+WebアプリケーションでのWebSocket APIの使用+]
* link:javaee-intro.html[+Java EEテクノロジ入門+]
* link:javaee-gettingstarted.html[+Java EEアプリケーションの開始+]
* link:../../trails/java-ee.html[+Java EEおよびJava Webの学習+]

Java EEの使用に関する詳細は、link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EEチュートリアル+]を参照してください。

link:../../../community/lists/top.html[+nbj2eeメーリング・リストに登録する+]ことによって、NetBeans IDE Java EE開発機能に関するご意見やご提案を送信したり、サポートを受けたり、最新の開発情報を入手したりできます。

