GAE開発の落とし穴
Googleのクラウド環境をつかったGoogle App Engineによる開発するにあたり、初めての試みで苦悩する開発者達の経験をもとに、各開発フェーズにあわせて問題点やどう解決したかをご紹介します
ブックマークされました!
ブックマークがエラーになりました。もう一度お願いします。
作者 川尻 剛 投稿日 2008年6月13日
このうち、持続的なHTTPコネクションを使用する、つまりCometと呼べる方法は後ろの2つです。まずは各手法の特徴を順に見ていきましょう。それぞれの手法の動作を次の図に示します。
まず最初に、ポーリングは定期的にリクエストを送信してイベントが発生したかどうか確かめる方法です。例えば、javascriptの setInterval関数を使用してXHRリクエストを何度も送信することなどが相当します。従来のリクエスト-レスポンス型に近い形でクライアントとサーバの両端を実装できるので実現が容易ですが、無駄なリクエストとレスポンスが発生してしまう欠点があります。また、リクエストを送信する間隔を短くするとサーバの負荷が高くなり、長くするとイベントの通知が遅くなるというジレンマを抱える事になります。
続いて、ロングポーリングはサーバーがイベントが発生するまでレスポンスを返すのを保留して、クライアント側はレスポンスが返ると同時に再度リクエストを送信する方法です。例えば、XHRのコールバック関数で再度通信を行うことなどが相当します。ポーリングと違い、無駄なレスポンスを排除する事ができますが、リクエストの方は依然として残る欠点があります。また、サーバー側はイベントが発生するまでの間にスレッドをブロックするので、すぐに接続キューの空きが不足してしまう問題を抱えます。
最後に、ストリーミングはコネクションを張りっぱなしの状態にして、イベントが発生するたびにレスポンスを返す方法です。本法ではクライアント側において、全てのレスポンスが届く前にデータを解析する必要があるので、少々トリッキーな実装を行う必要があります。例えばIEのXHR通信では、全ての読み込みが完了するまでコールバック関数は実行されません。未完了のレスポンスを読み込むにはいくつか方法がありますが、最も簡単なのはiframeを使う方法です。
本手法では空のiframeとレスポンスの処理を記述した関数を予めページに記述しておき、Comet処理が必要となった時点でiframeのsrc属性にサーバーへのパスを指定します。すると自動的にGETリクエストが送信されるので、サーバは応答を返さずに保留させ、イベントが発生するたびにレスポンスを返すようにします。このレスポンスはiframeの中に流れていくので、レスポンスに親ページで定義した関数を呼び出す内容を含めておくと、それが到着するごとに解析処理が実行されます。
以上のような方法で実現できるストリーミングですが、無駄なリクエストとレスポンスを完全に排除できる一方で、ロングポーリングと同様のスレッド問題が残ります。この問題を解決するために、現在は以下のようなアプリケーションサーバが提供されています。
package web;次にクライアント側の準備をします。先に述べたように、ストリーミング方式では予めレスポンスを解析する処理とiframeをページに記述しておきます。index.htmlというファイルを作成し、次のように内容を修正して下さい。
import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class StreamingServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger("servlet");
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">iframeのsrc属性にサーブレットのパスを指定しているので、ブラウザで開いた瞬間にdoGetメソッドが実行されます。ここで一度アプリケーションを起動して、さきほど追加したログが出力されるか確かめて下さい。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>HTTP Streamingのサンプル</title>
<script type="text/javascript">
function update(message) {
alert(message);
}
</script>
</head>
<body>
<iframe src="streaming" frameborder="0" width="0" height="0"></iframe>
</body>
</html>
private String cometContextPath;CometContextを作成したら、次にCometHandlerを定義します。CometHandlerは com.sun.grizzly.comet.CometHandlerインターフェイスを実装する事で表現する事ができます。インターフェイスのAPI のうち、onEventメソッドがイベントの発生時に呼び出されるメソッドです。FormHandlerというCometHandler実装クラスを作成し、次のようにイベント発生時にクライアントのupdate関数を実行するように修正して下さい。
@Override
public void init(ServletConfig config) throws ServletException {
cometContextPath = config.getServletContext().getContextPath() + "/streaming";
CometContext context = CometEngine.getEngine().register(cometContextPath);
context.setExpirationDelay(10 * 60 * 60 *1000);
}
Package web.handler;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
import java.io.IOException;
import java.io.PrintWriter;
public class FormHandler implements CometHandler<PrintWriter> {
private final static String SCRIPT_START_TAG = "<script type='text/javascript'>";
private final static String SCRIPT_END_TAG = "</script>";
private PrintWriter writer = null;
public void attach(PrintWriter writer) {
this.writer = writer;
}
public void onEvent(CometEvent event) throws IOException {
if (event.getType() == CometEvent.NOTIFY) {
writer.write(SCRIPT_START_TAG);
writer.write("window.parent.update('Hello Comet!');");
writer.write(SCRIPT_END_TAG);
writer.flush();
}
}
public void onInitialize(CometEvent event) throws IOException {
}
public void onTerminate(CometEvent event) throws IOException {
onInterrupt(event);
}
public void onInterrupt(CometEvent event) throws IOException {
writer.close();
event.getCometContext().removeCometHandler(this);
}
}
@Override登録したCometHandlerはCometContextのnotifyメソッドを実行する事で呼び出す事ができます。次の図に示すように、 CometContextは登録された全てのCometHandlerを呼び出します。なお、特定のCometHandlerを実行したい場合は addCometHandlerの返り値にIDが返りますので、それを退避してnotifyメソッドに指定して下さい。
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
FormHandler handler = new FormHandler();
handler.attach(response.getWriter());
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.addCometHandler(handler);
}
@Override後はXHRなどでPOSTリクエストを送信するようにページにボタンを付け加えると、ボタンが押されるたびに全てのブラウザに「Hello Comet!」と書かれたダイアログが表示されるようになります。なお、今回は固定値を表示しましたが、動的なデータをCometHandlerに渡したい場合は、notifyメソッドの引数にデータを指定します。するとonEvent側では次のようにデータを参照することができます。
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.notify(null);
}
MyClass output = (MyClass) event.attachment();一例として、Prototype.js(Script.aculo.us)とJson-libを使用してフォームの内容をCometHandlerに渡すサンプルを以下に示します。これらを参考に、自分だけのCometアプリケーションを作成してみて下さい。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cometフォーム</title>
<script src="lib/prototype.js" type="text/javascript"></script>
<script src="lib/scriptaculous.js?load=effects" type="text/javascript"></script>
<script type="text/javascript">
var CometForm = Class.create();
CometForm.prototype = {
initialize: function(url,root,button) {
this.url = url;
this.root = root;
Event.observe($(button), 'click',
this.kick.bindAsEventListener(this), false);
},
connect: function(iframeEl) {
$(iframeEl).src = this.url + '?date=' + new Date().getTime();
},
kick: function() {
var query = Form.serialize(this.root);
new Ajax.Request( this.url, {
"method": "post",
"parameters": query
});
}
}
function update(obj) {
new Effect.Highlight(this.root);
$('id').value = obj.id;
$('name').value = obj.name;
$('tel').value = obj.tel;
}
Event.observe(window, 'load', function() {
var myform = new CometForm('streaming','address','exec');
myform.connect("connecter");
});
</script>
</head>
<body>
<iframe id="connecter" frameborder="0" height="0" width="100%"></iframe>
<table id="address">
<tr>
<td>id</td>
<td><input type="text" name="id" id="id" /></td>
</tr>
<tr>
<td>氏名</td>
<td><input type="text" name="name" id="name" /></td>
</tr>
<tr>
<td>電話番号</td>
<td><input type="text" name="tel" id="tel" /></td>
</tr>
</table>
<button id="exec">サンプル実行</button>
</body>
</html>
package web;
import com.sun.grizzly.comet.CometContext;
import com.sun.grizzly.comet.CometEngine;
import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import web.handler.FormHandler;
public class StreamingServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger("servlet");
private String cometContextPath;
@Override
public void init(ServletConfig config) throws ServletException {
cometContextPath = config.getServletContext().getContextPath() + "/streaming";
CometContext context = CometEngine.getEngine().register(cometContextPath);
context.setExpirationDelay(10 * 60 * 60 *1000);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info("connect");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
FormHandler handler = new FormHandler();
handler.attach(response.getWriter());
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.addCometHandler(handler);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Address address = new Address();
address.setName(request.getParameter("name"));
address.setId(request.getParameter("id"));
address.setTel(request.getParameter("tel"));
CometContext context = CometEngine.getEngine().getCometContext(cometContextPath);
context.notify(address);
}
}
package web.handler;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;
import net.sf.json.JSONObject;
import web.Address;
public class FormHandler implements CometHandler{
private PrintWriter writer = null;
private final static Logger logger = Logger.getLogger("handler");
private final static String SCRIPT_START_TAG = "";
public void attach(PrintWriter writer) {
this.writer = writer;
}
public void onEvent(CometEvent event) throws IOException {
if (event.getType() == CometEvent.NOTIFY) {
if (event.attachment() instanceof Address) {
Address output = (Address) event.attachment();
JSONObject jsonObject = JSONObject.fromObject(output);
logger.info(jsonObject.toString());
writer.write(SCRIPT_START_TAG);
writer.write("window.parent.update(" + jsonObject + ");");
writer.write(SCRIPT_END_TAG);
writer.flush();
}
}
}
public void onInitialize(CometEvent event) throws IOException {
}
public void onTerminate(CometEvent event) throws IOException {
onInterrupt(event);
}
public void onInterrupt(CometEvent event) throws IOException {
writer.close();
event.getCometContext().removeCometHandler(this);
}
}
Googleのクラウド環境をつかったGoogle App Engineによる開発するにあたり、初めての試みで苦悩する開発者達の経験をもとに、各開発フェーズにあわせて問題点やどう解決したかをご紹介します
去る1月12日、定理証明支援系ツールCoqの初心者向けチュートリアルが開催さ れた(http://kokucheese.com/event/index/23667/)。今後も2月2日 (http://kokucheese.com/event/index/23744/)、2月9日、2月16日と引き続き開 催されていく予定である。本記事では、開催の様子をレポートする。
Neal Gafter氏はOracleによるJava買収の影響に関する議論、Javaにセグメンテッドスタックやメタオブジェクトプロトコルを追加することについての主張、そしてJavaとC#との比較について話をしてくれた。
GoogleはVMをともなう新しい言語であり、JSコンパイラでもあるDartをプレビューした。 InfoQはDartのアプリの構築に貢献する文法の裏側を探った:スナップショット、Isolate、モジュール方式
本記事ではCSPベースの「マルチドメイン・モデル検査ツール」である、PAT(Process Analysis Toolkit)について紹介する。モデル検査は、形式手法(Formal Method)という方法論を基礎とする技術であり、複雑さが増大しながらも安全性を求められる、現在のソフトウェア開発の状況に対する処方箋の1つとして注目されている手法である。
前回まで、Jenkinsの幾つかの側面に注目して解説をしてきました。シリーズ最後の今回は、Jenkinsをサービスとして使う方法を紹介します。
Alloyは、MITにて開発された仕様記述言語であり、ツールによる自動解析を使い、インクリメンタルに形式仕様が書けることが特長である。筆者らはAlloy開発者による、Alloyを使った形式手法入門書を翻訳、今夏にオーム社より刊行した。本記事では、Alloyの簡単な概要と、翻訳書『抽象によるソフトウェア設計』(「Alloy本」)を紹介する。
スマートフォンを中心としたマルチデバイスにおけるタッチユーザーインターフェイスへの対応は、既に必須の項目となりつつある。本記事では、Windows デバイスにおける UX のベースとなっている「メトロ」というデザイン言語を掘り下げながら、既存環境を意識しつつもどのようにタッチユーザーインターフェイス開発に取り組んでいくべきであるかについて解説していく。
No comments
スレッド表示 返信