FirebaseプログラムをApp Engineにディプロイする

Firebaseは、Googleクラウドサイトにディプロイして稼働させることもできます。GoogleクラウドとしてはPaaSのApp EngineとIaaSのCompute Engineどちらも使用できます。今回はEclipseで作成したFirebaseプログラムをApp Engineにディプロイし、これまでと同様の処理ができるかを確認します。
また、データの項目数を増やした場合にどのようなデータ格納形式および画面表示になるのか、さらに第1回で解説したネットワーク障害への対応について、インターネット接続を故意に切断した後に再接続した場合の動き(オフライン機能)も確認します。
App EngineでFirebaseを稼働させる
App EngineからFirebaseへの情報発信は簡単です。第2回のリスト1で紹介したようにFirebaseのリアルタイムデータベースへアクセスし、クライアントコード内にJavaScript Client Libraryをロードする記述を追加するだけです。
ただし、サーバ側にコード記述が不要なFirebaseをEclipseからApp Engineにディプロイするためには、ちょっとしたプロジェクト記述の変更が必要になります。もっとも、このような変更もApp Engineのプログラム作成に慣れた人であれば容易に分かるレベルです。
Eclipseの設定
App EngineにディプロイするFirebaseのプログラムはJavaコード用のEclipseから作成します。なお、必要なモジュールやライブラリのインストール・設定について詳細を解説するとかなりのボリュームになるため、ここでは簡単に「どのようなモジュールが必要か」だけを記述します。インストールや設定については、他のサイトあるいは書籍等を参照してください。
筆者は、Windows 10環境で下記のようにインストールと設定を行ないました。
1.Javaのインストール
Eclipseを使用するにはJavaをインストールする必要があります。筆者はバージョンをJava 8(1.8)にしました。皆さんの環境でもJava 8を使用するようにしてください。FirebaseではJavaを使用しませんが、JavaによるApp Engineプログラムの開発を想定した環境を構築するために必要となります。
2.Eclipseのインストール
Java 8に続いてEclipseをインストールします。執筆時点(2016年3月)におけるEclipseの最新バージョンはMars.2(4.5.2)で、筆者はこのバージョンを使用しています。
なお、Eclipseを日本語化する「Pleiades All in One」などもありますが、筆者は使用していません。Googleの主要な解説はすべて英文であり、英文表記の方が対応も明確で使用しやすく、間違いがないためです。また個人的な経験としてPleiades All in Oneで発生した問題を英語版のEclipseでは解決できたことなどもあります。 File、Edit、Runのような簡単な英語表記をわざわざ日本語表記に変えるメリットはありませんし、GoogleやAmazonのクラウド等を扱うには英文に慣れておく必要もあるでしょう。
3.EclipseにGoogle Plugin for Eclipseをインストール
Eclipseのメニューから[Help]→[Install New Software]を選択します。Google Plugin for Eclipseサイトには執筆時点でインストール用としてLuna(Eclipse 4.4)までしかありませんが、Mars.2でも問題なく動きます(スタック・オーバーフローにも記述あり)。
具体的なインストール方法については、Google Plugin for Eclipseサイトを参照してください。
4.Eclipseの環境設定
続いて、Windowsメニューの[Preference Eclipse]から環境設定を行います。
(1)App Engineプラグインを選択「App Engine」で使用するプラグインを選択します。3.でインストールしたプラグインを指定します(図1)。
(2)Java Compilerのレベルを設定「Compiler」でCompiler Compliance levelを設定します。「1.8」を選択します(図2)。
(3)インストール済みのJREを指定「installed IREs」でインストール済みのJRE(Java Runtime Environment)を指定します(図3)。
(4)文字コードの指定「Workspace」で文字コードを指定します(図4)。文字コードにはUTF-8を使用しますが、画面下部の「Text file encoding」で「Other」→「UTF-8」を選択します。
以上でEclipseの環境設定は終了です。続いて、Firebaseプログラムを作成していきましょう。
Firebaseプログラムの作成
プログラムのプロジェクト構成
作成するFirebaseプログラムのプロジェクト構成は図5のようになっています。記述するプログラムは「Firebase2.html」のみです。基本的なプロジェクト構成はApp Engineプログラムを作成する場合とほとんど同じですが、BaaSのFirebaseではサーバ側のプログラムが不要なため、本来はsrcフォルダ下にあるJavaプログラムはすべて削除しています。ただし、削除していなくてもweb.xmlの記述で対応可能です。
Firebaseのプログラム(firebase2.html)を見ていく前に、ここでクラウド上のディプロイ環境とプロジェクトの動作環境を定義する「web.xml」と「appengine-web.xml」について解説します。
web.xml(デプロイメントディスクリプタ)
Webアプリケーションの各種設定を行うXMLファイルです(リスト1)。WEB-INFディレクトリの直下に配置され(図5)、サーブレット名とクラス名の関連付けや初期設定を記述します。
リスト1:web.xml(デプロイメントディスクリプタ)
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!--
<servlet>
<servlet-name>Helloworld</servlet-name>
<servlet-class>com.appengine.HelloworldServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Helloworld</servlet-name>
<url-pattern>/helloworld</url-pattern>
</servlet-mapping>
-->
<welcome-file-list>
<welcome-file>firebase2.html</welcome-file>
</welcome-file-list>
</web-app>
リスト1はFirebaseで使用するweb.xmlです。デフォルトで生成されるサーブレット関係の記述はすべてコメントアウトしており、サーバ側プログラム(サーブレットやビーンズ等)があっても動作上問題はありません。ここにはFirebaseのクライアントとして動作するfirebase2.htmの指定のみ<welcome-file>タグに記述されています。
appenhine-web.xml
App Engineクラウド環境へのディプロイ情報やマルチスレッドでの動作可否、ロギングについて記述します。Firebase用に記述するのは<application>タグと<version>タグの内容だけで、他はすべてデフォルトで生成された記述そのままです(リスト2)。<threadsafe>タグは1つのインスタンスで複数のリクエスト送信を可能にするパラメータでtrue(有効)になっていますが、BaaSのForebaseではtrueとfalseどもちらでも問題なく動作します。
リスト2:appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>sonic-solstice-422</application>
<version>firebase2</version>
<!--
Allows App Engine to send multiple requests to one instance in parallel:
-->
<threadsafe>true</threadsafe>
<!-- Configure serving/caching of GWT files -->
<static-files>
<include path="**" />
<!-- The following line requires App Engine 1.3.2 SDK -->
<include path="**.nocache.*" expiration="0s" />
<include path="**.cache.*" expiration="365d" />
<exclude path="**.gwt.rpc" />
</static-files>
<!-- Configure java.util.logging -->
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
<!--
HTTP Sessions are disabled by default. To enable HTTP sessions specify:
<sessions-enabled>true</sessions-enabled>
It's possible to reduce request latency by configuring your application to
asynchronously write HTTP session data to the datastore:
<async-session-persistence enabled="true" />
With this feature enabled, there is a very small chance your app will see
stale session data. For details, see
http://code.google.com/appengine/docs/java/config/appconfig.html#Enabling_Sessions
-->
</appengine-web-app>
リスト2の<application>タグはProject IDに対応しており、ここでは「sonic-solstice-422」を選択しています。またバージョンは<version>タグで任意に指定できます。数値でなくても構いません。ここでは<version>タグを「firebase2」にしています。
App Engineへのディプロイ
Eclipse側の処理はすべて終了したので、いよいよApp Engineへのディプロイを行います。EclipseメニューのApp Engineアイコン( )をクリックして「Deploy to App Engine」を選択すると(図6)、プロジェクトのクラウド上へデプロイが開始されます。ディプロイが完了すると自動的にFirebaseの画面が表示され、すぐに使えるようになります。
図7はディプロイ完了後のApp Engine Versions画面です。Versionsの欄にはappengine-web.xmlに新規で登録した「firebase2」が表示されていますが、これをクリックすると図8の画面が表示されます。
画面表示の修正
なお、App Engineから表示される画面(図8)は内容を若干修正しています。修正内容は次のようになります。
- チャット入力フィールドを画面上部に移動して年月日と時間のフィールを追加
- 投稿済みチャットメッセージの行間を開けて見やすく表示
- 新規に投稿する場合は画面上部右の「書き込み」ボタンをクリック
イタリック体で「undefined」と表示されている部分は今回追加した年月日時間フィールドです。前回までのチャット画面ではこの項目がありませんでした。
チャット投稿後の画面表示
チャットを新規で入力・投稿すると、投稿内容が他のユーザ画面にもリアルタイムで表示されます(図9)。最下部に最新のチャットメッセージと年月日、時間も表示されていることを確認できます。これらのデータはJSONフォーマットのデータベースにも同様に書き込まれています。
ソースコードの確認
ここまでの解説でプログラムの表示や動作についてはイメージが掴めたと思います。次に、ソースコードを確認してみましょう(リスト3)。
リスト3:firebase2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src='https://cdn.firebase.com/js/client/2.2.1/firebase.js'></script>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>
<title>Firebase Simple apli</title>
</head>
<body bgcolor="#bfefdf">
<h1>Firebase Simple Chat</h1>
<div> <! --(1)-->
名前 <input type='text' id='nameIn' />
<nobr id='dtShow'></nobr> <! --(2)-->
<input type='button' id='sendMsg' value=' 書込み ' /><br/> <! --(3)-->
<textarea id='msgIn' rows='5' cols='70'></textarea> <! --(4)-->
</div> <! --(1)-->
<script>
var dStore = new Firebase('https://fbase-kseino1.firebaseio.com/');
var dt = new Date(); //(5)
var year = dt.getFullYear(); //(6)
var month = dt.getMonth()+1; //(7)
var day = dt.getDate(); //(8)
var hour = dt.getHours(); //(9)
var minute = dt.getMinutes(); //(10)
var second = dt.getSeconds(); //(11)
var dtime = year+'年 '+month+'月'+day+'日'+hour+'時'+minute+'分'+second+'秒'; //(12)
$('#dtShow').text(dtime); //(13)
$('#sendMsg').click(function (e) {
var name = $('#nameIn').val();
$('#dtShow').text(dtime); //(14)
var text = $('#msgIn').val();
dStore.push({name: name, dtime: dtime, text: text}); //(15)
$('#msgIn').val('');
});
dStore.on('child_added', function(snapshot) {
var msg = snapshot.val();
dspChatMsg(msg.name, msg.dtime, msg.text); //(16)
});
function dspChatMsg(name, dtime, text) {
$('<div/><br/>').text(text).prepend($('<em/>').text(dtime +': ')).prepend($('<em/>').text(name+': ')).appendTo($('#msgDiv')); //(17)
$('#msgDiv')[0].scrollTop = $('#msgDiv')[0].scrollHeight;
};
</script>
<hr/>
<div id='msgDiv'></div>
<hr/>
</body>
</html>
リスト3は前回のコード(リスト1:index.html)を修正したものです。修正部分を中心に簡単に確認します。
【画面表示タグの追加と変更】
(1)チャット入力フィールドを画面上部に移動
(2)ID値=’dtShow’で年月日時間の表示フィールドを指定
(3)ボタンクリックしてデータ登録する際のボタンを指定
(4)チャット入力フィールドを複数行入力可能な<textarea>タグに変換
【年月日時間の取得と変数代入】
(5)new Date()でDateオブジェクトを生成
(6)~(11)年、月、日、時間、分、秒を取得して変数にセット
(12)表示用の変数dtimeにセットし(13)で表示
(16)データストアへの登録
(17)年月日時間情報の画面表示
オフライン処理の動作確認
連載第1回でも解説したように、Firebaseはネットワーク障害時の対応機能(オフライン処理)を備えています。ネットワーク接続が切断されると、画面入力後登録処理されたデータはまずオフラインのデータベース(ローカルストレージ)に書き込まれます(図10)。
その後サーバとの接続が回復するとデータがローカルデータベースからサーバのリアルタイムデータベースへ送られ、書き込み処理とサーバプッシュによるレスポンス処理が行われます(図11)。
ここではリスト3のコードをレスポンスタイミング等が分かるように一部修正し、実際にインターネット接続を切断してどのような応答結果になるかを見ていきます。
コードの追加・修正
リスト4はオフライン処理時の動作を確認するために追加・修正を加えたコードです。
リスト4:Firebase2-1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src='https://cdn.firebase.com/js/client/2.2.1/firebase.js'></script>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>
<title>Firebase Simple apli</title>
</head>
<body bgcolor="#bfefdf">
<h1>Firebase Simple Chat</h1>
<div>
名前 <input type='text' id='nameIn' />
<input type='button' id='sendMsg' value=' 書込み ' /><br/><br/>
<em>
画面表示 :<nobr id='dtShow1'></nobr></b><br/> //(1)
登録処理 :<nobr id='dtShow2'></nobr><br/> //(2)
レスポンス:<nobr id='dtShow3'></nobr><br/><br/> //(3)
</em>
<textarea id='msgIn' rows='5' cols='70'></textarea>
</div>
<script>
var dStore = new Firebase('https://fbase-kseino1.firebaseio.com/');
$('#dtShow1').text(getDtime()); //(4)
$('#dtShow2').text('');
$('#dtShow3').text('');
$('#sendMsg').click(function(e){
var name = $('#nameIn').val();
var dtime2 = getDtime(); //(5)
$('#dtShow2').text(dtime2); //(5)
var text = $('#msgIn').val();
dStore.push({name: name, dtime: dtime2, text: text});
$('#msgIn').val('');
});
dStore.on('child_added', function(snapshot){
var msg = snapshot.val();
dspChatMsg(msg.name, msg.dtime, msg.text);
});
function dspChatMsg(name, dtime, text){
var dtime3 = getDtime(); //(6)
$('#dtShow3').text(dtime3); //(6)
$('
').text(text).prepend($('').text(dtime +': ')).prepend($('').text(name+': ')).appendTo($('#msgDiv'));
$('#msgDiv')[0].scrollTop = $('#msgDiv')[0].scrollHeight;
};
function getDtime(){ //(7)
var dt = new Date();
var year = dt.getFullYear();
var month = dt.getMonth()+1;
var day = dt.getDate();
var hour = dt.getHours();
var minute = dt.getMinutes();
var second = dt.getSeconds();
var millseconds = dt.getMilliseconds();
var dtime = year+'年 '+month+'月'+day+'日 '+hour+'時 '+minute+'分 '+second+'秒 '+millseconds+'ミリ秒';
return dtime;
}
</script>
<hr/>
<div id='msgDiv'></div>
<hr/>
</body>
</html>
主な修正部分は年月日時間の表示です。HTMLタグでは(1)~(3)で表示フィールドを追加し、JavaScriptコードでは年月日時間の取得処理を(7)getDtime()関数で行うように変更しています。あとはgetDtime()関数を使用して(4)(5)(6)でそれぞれのタイミングに応じた時間を取得して表示しています。
オフライン処理時の画面表示
ここでは、上記の修正コードを使用した画面処理を見ていきます。
オンライン処理の画面表示を確認する
まず、通常のオンライン処理における画面表示を確認しましょう。図12の画面左側はApp Engineにディプロイされた画面の表示内容、右側はGoogle Compute Engine(GCE)にディプロイされた画面の表示内容です(GCEについては次回で解説します)。まず、App Engine側で名前とメッセージを入力します。
メッセージを入力後、「書込み」ボタンをクリックした後の画面が図13です。ここで注目すべきはメッセージ入力欄の上にある「画面表示」「登録処理」「レスポンス」のフィールドで、App Engine(画面左)に比べてGCE(画面右)のレスポンスにやや時間を要していることが確認できます。
オフライン処理の画面表示を確認する
次にインターネット接続がないオフライン状態でメッセージを入力し、その後オンライン状態に復旧した場合の画面表示です。オンライン時と同様に画面左のApp Engine側からデータを入力します。その際にメッセージを2つ連続して入力してみます。画面下部に表示される最新のメッセージを見ると、画面左のApp Engine側にはローカルDBに登録されたメッセージが表示されていますが、画面右のGCE側にはメッセージが表示されていません(図14)。
オンライン復旧後の画面表示を確認する
図15はオンライン復旧後の画面表示です。画面上部のレスポンス時間は最下部のメッセージにも表示されていますが、App EngineよりGCEの方が数分遅れていることを確認できます。筆者はVDSLをオフライン状態にしてこのテストを行いましたが、オンライン状態への復旧が他の画面表示から確認できてもすぐにGCE側へメッセージが送付されるのではなく、送付と表示にやや時間が掛かっています。
今回はApp EngineにFirebaseのプログラムを配置し、実際にオンライン/オフライン環境を構築して処理を行いました。Firebaseのリアルタイムデータベース機能を確認できたのではないかと思います。
次回は、今回解説したオフライン処理で使用したGoogleのIaaSクラウド「Google Compute Engine(GCE)」の基本処理とプログラムのディプロイ方法を解説します。また、UI表示にはこれまでPC画面を使用していましたが、次回はスマートフォン画面を使用してFirebaseのリアルタイム機能におけるPC画面との連動なども確認していきます。


