1. GAE/Jのページ

(作成中です!)
GAE/Jを勉強がてら触ってみたので、このページにはそこで気付いたことなどを備忘録的に載せていきます。
※間違いがあっても読んだ人の自己責任でお願いします。また、やさしくご指摘いただけるとと幸いです。



1.1. 概要

GAE(Google App Engine)は、Google社が提供する、自分が開発したWebアプリケーションを実行・公開できるクラウドサービスです。
アプリケーションは、javaかpythonで開発します。
# 私は、perl好き、pythonは知らない、javaは1.2くらいのときに開発したけど最近は・・・という感じなので、とりあえずjavaで。
特徴としてはいくつかありますが、私が重要だと思う特徴は下記です。

(1) Googleのインフラが簡単に使える
サーバ構築が不要です。つまりその分のコストと時間がかかりません。
1アカウントで10アプリまでつくれるようです。

(2) ある程度までは無料でつかえる
Googleのページによるとおよそ月500万ページビューまでのWebアプリまで無料で利用できる(2011/9/1現在)と書かれています。
もちろんどんな処理をするかによるので、ページビューは目安です。CPU 時間、受信帯域幅、発信帯域幅、ストレージ、メールへの追加割り当てを課金することで増やせるようです。まだ、無料状態しかしらないので詳しくはわかりません。

(3) Google Plugin for Eclipseが無償で提供されている
javaで開発するなら、無償で提供されているGoogle Plugin for Eclipseを使って、Eclipseでの開発が容易にできる

(4)データベースはBigTableに限られる
RDBに慣れているとちょっと視点をかえて設計する必要がありますが、慣れれば実は考えやすいというかわかりやすい面があるように、今のところは思ってます。

(5)Servlet API仕様に則っている(?!)
sevletは、普通に、javax.servlet.http.HttpServlet を extends したclassをつくって、web.xmlにservletとservlet-mappingを書いてやれば普通に動きます。JSPも普通に動きますね。sessionにはSeriarizableなインスタンスしか設定できないとか制約はありますが、基本は同じなので、JavaでWebの開発をしたことがあればすんなり入れると思います。

(6) 処理時間の制限
処理時間の制限があります。Googleのページに「Web リクエストの処理のために呼び出された場合は、30 秒以内にレスポンスを発行しなくてはなりません」と書かれています。

(6)ファイルにアクセスできない
サーバ上にファイルを作成したり、それにアクセスしたりということはできません。BigTable内にBlob型で保存すれば、ある程度のことはできますが、ちょっと不便ですね。

(7)Cron、Task Queue
まだ良く知らないのですが、裏で処理を実行できるとのこと。Cronはunixでいうcronのイメージで、時間起動で処理を行えるものですね。Task Queueは、リソースに余裕があるときに実行したり、実行時にエラーになると、間隔をおいて再実行したりすることができるもののようです。そのうち試してみたいとおもいますが、Task Queueを使いこなすを結構いろいろできそうな気も・・・。


1.2. 環境

開発環境は以下の通りです。

・OS:Windows7(64bit)

・Eclipse:Pleiades All in One 3.5.2.20100226
めんどくさいので、All in Oneをいれました。
http://mergedoc.sourceforge.jp/

・JRE:1.6.0_18
Pleiades の付属です。
JREの設定は、defaultで1.5なので1.6に変更しました。
  [ウインドウ]→[設定]→[Java]→[インストール済みのJRE]→1.6をチェック

・Google Plugin for Eclipse
googleのページには、Eclipse3.3用と3.4用のURLしか書いてありませんが、3.5もあります。
http://dl.google.com/eclipse/plugin/3.5
Web Tools Platform(WTP)プラグインも入れました(googleのページに推奨とかいてあるので)

・App Engine SDK:1.5.2
pluginに含まれます。

他に使っているライブラリは、下記です。
・commons-fileupload-1.2.2.jar
アップロードファイルの取得用
http://commons.apache.org/fileupload/
・jxl.jar
2.6.12
Excelファイルの操作用
http://jexcelapi.sourceforge.net/
jexcelapi_2_6_12.zip を取得し回答して、jxl.jarをWEB-INF/libに配置します。


1.3. 備忘録

気になったので調べたことや、はまったことを載せていきます。

◆環境構築時

開発環境の構築は、Googleのページの通りにやっていけば、「まぁ」できます。
「まぁ」といっているのは、EclipseからGoogle Pluginをインストールする際に何度か失敗しました。
何回かやればうまくいくのですが、わからなかったので、Eclipseから入れ直しを数回やりました・・・T_T;
あと、Googleページに書いてありますが、PJを作る際に、「[Google Web ツールキットを使用する(Use Google Web Toolkit)] をオフ」にしないと、実行するときにエラーになるので注意です。


multipartのエンコーティング

commons-fileuploadを利用して、ファイルのアップロードを試しました。
ファイルのアップロードはなにも問題なくできますが、multipartでファイル以外に受け取るパラメータの値が日本語だと文字化けしました。これは開発環境なら文字化けしませんが、本番に上げると文字化けしました。
ファイルをアップロードしてメールに添付というのを試していたので、メール送信時に化けたのだとばかり思っていたので解決するのに、時間がかかってしまいました・・・T_T;

multiの場合は、そのまま渡すから自力で何とかしろって感じでしょうか。
iso-8859-1でgetBytesして、utf-8でStringをつくれば対応できます。

<サンプル>
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iterator = upload.getItemIterator(request);

while (iterator.hasNext()) {
	FileItemStream item = iterator.next();
	InputStream stream = item.openStream();
	String fieldName = item.getFieldName();

	// フォームのパラメータ
	if (item.isFormField()) {
		String value = null;
		value = Streams.asString(stream, "iso-8859-1");
		value = new String(value.getBytes("iso-8859-1"), "utf-8");

        ・・・

	// ファイルとファイルpartのヘッダ部分
	} else {
		fileName =  item.getName();
		contentType = item.getContentType();
		int len;
		byte[] buffer = new byte[1024];
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		while ((len = stream.read(buffer, 0, buffer.length)) != -1) {
			out.write(buffer, 0, len );
		}
		Blob fileContents = new Blob(out.toByteArray());

        ・・・

	}
	continue;
}

Streams.asString()はなくてもよいのですが、google側でdefaultのエンコーティングが変わった場合にもこれを入れておけば大丈夫かなということで入れときました・・・。

ちなみに、slim3ならこの辺はうまいことやってくれます。というか、slim3がInputStreamを全部読んでしまうようなので、自力で読みたくても読めません。



本番環境と開発環境の違いの判別

multipartのエンコーディングで触れましたが、開発環境と本番環境で動きが違う場合があるようです。そんな時は、その分岐を処理に入れてしまいたいですね。ということで調べた(getEnv()とgetProperties()の値を取得して、使えそうなものがないか探した・・・)ところ、System.getProperty("com.google.appengine.runtime.environment") で判断つきそうです。
開発環境の場合にこの値は、"Development"になります。
とりあえず、Utilityクラスでもつくって判別するstaticメソッドとつくっとくか~と作りましたが・・・、 slim3なら、Controller#isDevelopment()で判別できるようです・・・T_T。


セッションの利用
セッションを使う場合は、WEB-INF/appengine-web.xmlに、

<sessions-enabled>true</sessions-enabled>

の設定が必要です。
また、GAEではセッションの内容を、メモリ上だけでなく、データとして保存するので、Serializableをインプリメントしたクラスのインスタンスしか入れられません。
ちょっと手抜きでServletでJDOインターフェースを利用して取得したデータをそのままセッションにいれたら、「Serializableじゃない」とおこられました。取得したデータを別にコピーして入れれば問題ないですが、まぁ、普通セッションにそのままいれたりしませんね。ちなみに、開発環境だとエラーにならないので、ちょっと厄介でした。
ちなみに、セッションに入れられるサイズの制限もあるみたいです。制限のサイズまで調べていませんが、ちょっとまちがって、Blob型で画像ファイルを含むリストをいれたらエラーになりました。まぁこれこそこんなことはしませんね。

detachCopy

JDOインターフェースでデータに、PersistenceManager#close()をしたあとアクセスするとエラーになります。これを防ぐには、
  • 取得したデータに対して、PersistenceManager#detachCopy() or PersistenceManager#detachCopyAll() する
  • JDOのアノテーションにdetachable="true"をいれる
ちなみに、jdoconfig.xml に
<property name="datanucleus.DetachOnClose" value="true" />

を書けば、detachCopy()をいちいち呼ばなくても大丈夫というように書いてあるサイトもありましたが、効果はないようでした(ちょっとやっただけなので、設定ミスかもしれません)。



POSTの制限

POSTのサイズ制限があります。
appengine-web.xml に
<property name="maxFormContentSize" value="512000"></property>
を記述しても変わらないとか、開発環境だけで本番環境ならOKとか、multipartならOKとか、いろいろWebで探すと出てきますが、私は、本番でmultipartでちょっと大きめの写真を上げようとしたらエラーになりました。時間ができたら再度調査してみます。


Excelファイルの生成(POIは×、JExcelApi○)

GAEでExcelの出力ができるかを調べました。
POIは、GoogleのページにINCOMPATIBLEと書かれてますが、使うメソッドによってはいけるんじゃないかと試してみましたが、やっぱり駄目でした。POIでは、ホワイトリストにないメソッドを使っているらしいです。
他になにかないかなと探していると、JExcelApiというのを見つけたので、試してみました。
これは問題なく動きます。
GAE/J + slim3 + JExcelApi でも動きました。
POIの方が知名度も高いし、使いやすいと思いますが、まぁ仕方がないので、 GAEでExcelを操作したいときは、 JExcelApiをつかいましょう。

◆メールの送信

メールの送信は簡単です。ただし、メッセージ送信者のアドレスはアプリケーション管理者のメール アドレス、またはログイン中のユーザーの Google アカウントのメール アドレスしかだめです。

Google codeに載っているサンプルだと、いまいち使いづらい(というかimportのpackageがまちがってないか?javax.mail.MimeMessageではなくて、javax.mail.internet.MimeMessageだと思う)ので、
com.google.appengine.api.mail.MailServiceをつかって、添付ありとかなしとかに対応したクラスをつくって試してみました。ソースの一部↓

import java.util.ArrayList;
import com.google.appengine.api.mail.MailService;
import com.google.appengine.api.mail.MailServiceFactory;
import com.google.appengine.api.mail.MailService.Message;
import com.google.appengine.api.mail.MailService.Attachment;;

	private String from;
	private ArrayList<String> toList;
	private ArrayList<String> ccList;
	private String subject;
	private StringBuffer content;
	private ArrayList<Attachment> attachFileList;

  ・・・

		try {
			 MailService mailService = MailServiceFactory.getMailService();
			 Message msg = new Message();

			 // From
			 msg.setSender(from);
			 // To
			 msg.setTo(toList);
			 // Cc
			 msg.setCc(ccList);
			 // Subject
			 msg.setSubject(subject);

			 // Contents
			 // 本文
			 msg.setTextBody(content.toString());
			 if (attachFileList != null) {
				 // AttacheFile
				 msg.setAttachments(attachFileList);
			 }

			 // 送信
			 mailService.send(msg);
		} catch (Exception e) {
			・・・
		}


GAEメールで文字化けといった記事も良く見ますが、特に文字化けも大丈夫でした。届いたメールのヘッダはこんな感じ。
・添付なし
MIME-Version: 1.0
X-Google-Appengine-App-Id: XXXXXXXXXXXXXXXXXXXXXXX
Date: Tue, 06 Sep 2011 08:41:50 +0000
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
From: XXXXXXXXXXXXXXXXXXXXXXX
To: =?ISO-2022-JP?B?XXXXXXXXXXXXXX?= <XXXXX@XXX.co.jp>
Content-Type: text/plain; charset=ISO-2022-JP; format=flowed; delsp=yes
Content-Transfer-Encoding: 7bit

Mail test.
$B%F%9%H%a!<%k(B

 ・添付あり
MIME-Version: 1.0
X-Google-Appengine-App-Id: XXXXXXXXXXXXXXXXXXXXXXX
Date: Tue, 06 Sep 2011 08:43:37 +0000
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWEhPCVrGyhC?=
From: XXXXXXXXXXXXXXXXXXXXXXX
To: =?ISO-2022-JP?B?XXXXXXXXXXXXXX?= <XXXXX@XXX.co.jp>
Content-Type: multipart/mixed; boundary=20cf305e2551ddcb8004ac41d230

--20cf305e2551ddcb8004ac41d230
Content-Type: text/plain; charset=ISO-2022-JP; format=flowed; delsp=yes
Content-Transfer-Encoding: 7bit

Mail test.
$B%F%9%H%a!<%k(B

--20cf305e2551ddcb8004ac41d230
Content-Type: image/jpeg; name="Hydrangeas.jpg"
Content-Disposition: attachment; filename="Hydrangeas.jpg"
Content-Transfer-Encoding: base64

/9j/4AAQSkZJRgABAgEAYABgAAD/4RA2RXhpZgAATU0AKgAAAAgABwEyAAIAAAAUAAAAYkdGAAMA
AAABAAMAAEdJAAMAAAABACYAAIKYAAIAAAAWAAAAdpydAAEAAAAYAAAAAOocAAcAAAfSAAAAAIdp
・・・・



宛先のメアドは、"<日本語名> XXXX@XXX.co.jp"、Subjectは、"テストメール"、本文の二行目は
"テストメール"としました。送られたメールをみると、宛先もSubjectもISO-2022-JPでBase64エンコードされてますね。本文もISO-2022-JPで送られているのがわかります(そのまま張り付けたので上記は文字化けしてますけど)。
このあたりはGAEがうまいことやってくれるようです。



◆indexに関して

開発環境だとindexは自動で作成されるので気にする必要がありませんが、本番に載せるとindexがなくてエラーになるので注意が必要です。
開発環境ではWEB-INF/appengine-generated/datastore-indexes-auto.xmlに必要なindexを書き込んでおき、本番へのデプロイ時にそのファイルをWEB-INF/appengine-generated/datastore-indexes.xmlにコピーして一緒にアップするのがよさそうです。
datastore-indexes-auto.xmlは、datastore-indexes.xmlに<datastore-indexes>要素に属性 autoGenerate="true"となっているか、datastore-indexes.xmlがない場合に開発環境でクエリが実行されたときに作成されますので、
(1) 開発環境でautoGenerate="true"としたdatastore-indexes.xmlを用意
(2) 画面等の操作をする(網羅するのがポイント。降順、昇順両方つかうなら両方とも!)
(3) datastore-indexes-auto.xml をdatastore-indexes.xmlにコピー
(4) 本番にデプロイ

注意点として、datastore-indexes-auto.xml はいつの間にか中にが空になっていることがあります。消されるタイミングは不明です(開発のGAEを止めても、デプロイしても、Eclipseを再起動(関係ないだろうけど)しても普通消えないのですが、しばらく(数日)ほっといてからみたら中身が消えていました)。

ちなみに、slim3だと気にしなくて大丈夫っぽいですが、なんでだろう?!ちょっと調べてみないと、わかりませんので、調べたら、また。

◆indexに関して~その②
indexは、datastore-indexes.xmlに記載して本番環境にデプロイすると、本番環境で構築されますが、結構時間がかかります。データ量がすくなくてもかかるようです。
その間、リクエストがあり、構築中のインデックスをつかうクエリが発行されるとエラーになってしまいます。それを避けるには、デプロイ時にアプリのバージョンを一つ上げてアップし、GAEの管理者画面でインデックスの作成完了を確認してから、新バージョンを適用します。詳しくはGoogleのページ参照。

◆開発環境のデータベース
開発環境では、データは、WEB-INF/appengine-generated/local_db.binに格納されています。データを削除したい場合は、開発用のGAEを停止して、このファイルを削除します。開発中にデータモデルに属性追加等をして、意味不明なエラーになってしまった場合は一度削除するのがよさそうです(もちろんテストデータも消えますが・・・)。


prettyprintコピペ用

0 件のコメント:

コメントを投稿