JavaEEで簡単WebSocketサーバ


本エントリでは近代的なWebアプリケーションでは欠かせないWebSocketをJavaEEで実現する方法を説明します。

前段・WebSocketはなぜ生まれたか

伝統的なWebアプリケーションは、HTTPというプロトコルで成立していました。HTTPとは単純なリクエスト・レスポンスモデルであり「クライアントであるWebブラウザがWebサーバに対してリクエストを投げ、返って来たレスポンスを処理する」というモデルで成り立っていました。あくまでクライアントが処理の起点となっているのです。

一方、Webアプリケーションであっても「サーバ側で起きた何らかの出来事を、Webブラウザに通知したい」というニーズは常にありました。これを「サーバプッシュ」と言います。これまで、HTTPを使って擬似的なサーバプッシュを実現するために技術者は様々な工夫を凝らしてきました。しかしやはり、リクエスト・レスポンスモデルの下では無理があったのです。

このような経緯があり、Webブラウザでの真のサーバプッシュを実現するべく策定されたのがWebSocketというプロトコルです。

もちろんJavaEEでもWebSocketはサポートされています。

本エントリではJavaEE7で導入されたWebSocketサーバを簡単に実現する機能をご紹介します。

最小のサンプル

これまで紹介してきたように、JavaEEでは、ある機能を使うのにアノテーションを付与したクラスを単に作成するだけで良い、というスタイルが広く採用されています。WebSocketも例外ではありません。

下記は最小のサンプルです。

@ServerEndpointアノテーションを付けたクラスはWebSocketサーバとなります。このようなクラスをEndpointクラスと呼びます。

さらに@OnMessageアノテーションでクライアントから送信されたメッセージを処理するメソッドを指定しています。メッセージの処理内容は、送信されたメッセージの頭に「Re: 」を付けて送り返しています。

このクラスのテストのために、次のようなHTMLを作ってみましょう。

テキストフィールドに文字を入力して「送信」ボタンを押すと、先頭に「Re: 」を付けた文字列がアラートで表示されるサンプルです。

 


 

いかがでしょうか。いとも簡単にWebSocketを使ったアプリケーションが作れてしまうことが分かっていただけたかと思います。

ただ、このサンプルはWebブラウザからのリクエストが起点であるため、Ajaxでも実現可能でありWebSocketの旨味がないサンプルです。そこで以降では、WebSocketを使うための実践的な機能を紹介していきます。

@OnMessage以外のアノテーション

@OnMessageアノテーションの他にも@OnOpen/@OnError/@OnCloseというアノテーションがあり、これらのアノテーションが付与されたメソッドは、適切なタイミングでJavaEEコンテナから呼び出されるコールバックメソッドとなります。

下記に典型的なEndpointクラスの宣言方法を示します。

各メソッドはpublicである必要があります。

また@OnErrorコールバックメソッドは、Throwableを引数に取る必要があります。

それ以外のメソッドの引数は必要に応じて増減させることが可能です。例えば、@OnMessageのコールバックでSessionが必要なら、引数に追加してください。

URIテンプレート

接続先を分類するために、URLにパラメータを含めることが出来ます。

これを使うと、チャットルームの入室管理のようなことが比較的簡単に実装できます。簡単なチャットサーバのコードをご紹介します。

注目していただきたいのは次の2点です。

  • @ServerEndpointの引数にroom-descriptorというパラメータを含めている
  • メソッドの引数に@PathParam("room-descriptor")を指定することでパラメータの値を得ている

なおEndpointクラスはCDIによるスコープ管理が効きません。たとえシングルトンな作りにしたくても、それが叶わないのです。

しかしインジェクション対象にすることはできます。ここでは、チャットルーム毎のWebSocket接続を管理するクラス(WebSocketSessionManager)をシングルトンとして作成し、@Injectアノテーションでインジェクションしています。

WebSocketSessionManagerクラスのコードも掲載しておきます。

スレッドセーフにするために少し複雑なコードになっていますが、結局やっていることは部屋毎にWebSocket接続を束ねているだけなので、臆さず読み解いてみてください。

最後に、これをテストするためのHTMLを掲載します。

動作を見てみるときは、ぜひ複数のWebブラウザを開いてください。誰かの投稿が同じ部屋の全員に配信されることが分かると思います。とてもシンプルですが、チャットアプリケーションができました。

websocket_desc

DecoderとEncoder

WebSocketはテキストデータとバイナリデータをやり取りできます。一般的には扱いやすいJSON形式のテキストを使うことが多いでしょう。しかしEndpointクラスを普通に作ると、@OnMessageコールバックの引数や戻り値の型は、単なるString型です。せっかくJavaを使っているのですから、ここはデータクラスに変換して型安全にメッセージを扱いたいものです。

この要求に応えるのが、Decoder/Encoderです。

Decoderクラス

Decoderクラスにはクライアント(一般的にはWebブラウザ)から送られてきたメッセージ内容を任意のJavaオブジェクトに変換する処理を実装します。このクラスを作成すると、Endpointクラスの@OnMessageコールバックメソッドに引数にString以外の型が使えるようになります。

ここでは ClientMessageというクラスを導入して、Decoderクラスを作ってみます。なおJSONとJavaオブジェクトの変換にはJSONICというライブラリを使っています。

まずはClientMessageクラスです。getterとsetterのみの単なるデータクラスとして作りましょう。

次にDecoderです。JsonDecoderという名前で作ります。なお使い方は後で説明します。

なおJSON文字列とJavaオブジェクトの変換にはJSONICというライブラリを使っています。

Encoderクラス

次にEncoderクラスを作ってみます。

Encoderクラスには、Javaオブジェクトをクライアントに送信するメッセージに変換する処理を実装します。このクラスを作成すると、Endpointクラスの@OnMessageコールバックメソッドの戻り値にString以外の型が使えるようになります。

ここではServerMessageというクラスを導入して、Encoderクラスを作ってみます。

ServerMessageクラスも、getterとsetterのみの単なるデータクラスとして作ります。

次に、JsonEncoderを作りましょう。これも使い方は後で説明します。

DecoderとEncoderを使う

作成したDecoderとEncoderはEndpointクラスの中で使います。ポイントは次の2点です。

  • Endpointクラスの@ServerEndpointアノテーションのdecodersとencodersに作ったクラスを指定
  • @OnMessageコールバックメソッドの引数と戻り値にデータクラスを指定

それではサンプルコードを見てみましょう。

String型がなくなり、データクラスを通して型安全にデータ操作ができるようになりました。

テスト用のHTMLも掲載します。

動作させてみると、JSON形式のデータをやり取り出来ていることが分かると思います。

dec-enc

 

データ変換の流れのまとめ

登場人物が多くなり少し複雑になったため、データ変換の流れを整理してみました。

websocket_dec_enc_flow

 

変換処理の流れと、Decoder/Encoderがどのような役割を担っているかを理解いただけたでしょうか。

最後に

以上、JavaEEのWebSocketに関する機能をご紹介しました。

実戦ではもっとたくさんのことに気を配る必要があります。例えばエラー処理やスケーリング、認証、リクエスト内容の解析などの処理が必要です。

しかしWebSocketの根幹となる機能は、本エントリでご紹介した機能で充分カバーされていると思います。JavaEEで作るWebアプリケーションにもどんどんWebSocketを取り入れて、新しいユーザ体験を提供していきましょう。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">