Google App Engine Javaで遊んでみる

Google App Engineが、Javaにも対応したというニュースを帰りの電車の中で見ました。

Google App Engine Blog: Seriously this time, the new language on App Engine: Java™

上の記事は英語なので何が書かれてるのかよくわかりませんが、Dukeが飛行機に乗ってたので間違いないです。よく見るとGWTの箱も持ってますね。

Getting Started: Java - Google App Engine - Google Code

Getting Startを見ながら遊んでみます。

開発環境をダウンロードする

Downloads - Google App Engine - Google Codeから開発環境をダウンロードして
適当なフォルダに解凍します。
Eclipseのプラグインもあったのですが、インストールしようとしたらeclipseが固まってしまったので諦めました。残念です。

C:\work\download\appengine-java-sdk-1.2.0に解凍しました。

デモを動かしてみる

開発環境にデモがついてきました。これを動かしてみます。
コマンドラインから「bin/dev_appserver.cmd」を起動するとサーバーが動くみたいです。パラメータにwarフォルダのパスを渡します。

cd C:\work\download\appengine-java-sdk-1.2.0
bin\dev_appserver.cmd demos\guestbook\war
The server is running at http://localhost:8080/

おー!
動いたような気がする。

ブラウザでhttp://localhost:8080/を開いてみます。

おおおー!

挨拶を書き込むだけのアプリケーションですが、Googleアカウントでの認証ができたり、書き込んだデータがサーバーを再起動しても残ってたりとか、ちゃんとWebアプリケーションとして動いてるみたいです。すごい!

プロジェクトを作る

Creating a Project - Google App Engine - Google Code

Google App EngineはServlet標準にのっとった作りになってるそうです。いつものwarプロジェクトと同じフォルダ構成にして、いくつかの独自設定ファイルを置いておけばいいよー。みたいな事が書いてる気がしました。英語が不自由なので確かなことは何一つわかりませんが、多分そんな感じでしょう。

チュートリアルでは「Guestbook」というプロジェクトを作りながら進むようです。
「ecilpseのプラグインを入れた人は簡単にできるよー」と書いてましたが、私はインストールに失敗したのでコツコツと手作りでがんばるしかありません。格差社会です。

「demos/new_project_template」というフォルダがあったので、それをコピーしてフォルダ名を「Guestbook」にしました。

フォルダの中身はこんな感じです。

Guestbook/
 +build.xml
 +COPYING
 +html/
 | +index.html
 +src/
   +log4j.properties
   +logging.properties
   +META-INF/
   | +jdoconfig.xml
   +WEB-INF/
   | +appengine-web.xml
   | +web.xml
   +org/
     +example/
       +HelloAppEngineServlet.java
  • Guestbook/src/META-INF/jdoconfig.xml
  • Guestbook/src/WEB-INF/appengine-web.xml

上の2つは見慣れないファイルですが、それ以外はいつもどおりです。あのファイルにGoogle App Engineの設定を色々と書いていくのでしょう。そういえばpython版にも似たような名前のファイルがあったような気がする。確かyamlだったような。。。
htmlフォルダの中に静的なファイルを入れてビルドするときに直下にコピーするって感じなのだと思います。

javaのソースコードがありました。

  • Guestbook/src/org/example/HelloAppEngineServlet.java

中身を見てみます。

package org.example;

import java.io.IOException;
import javax.servlet.http.*;

public class HelloAppEngineServlet extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		resp.setContentType("text/plain");
		resp.getWriter().println("Hello, world");
	}
}

普通のサーブレットクラスでした。

コンパイル

テンプレートフォルダの直下にbuild.xmlがあったので、antを実行してみます。

cd C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook
ant
Buildfile: build.xml

compile:
    [mkdir] Created dir: C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\classes
    [mkdir] Created dir: C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\lib
    [javac] Compiling 1 source file to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\classes

enhance:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=16 ms, enhance=0 ms, total=16 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

war:
     [copy] Copying 1 file to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www
     [copy] Copying 2 files to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=16 ms, enhance=0 ms, total=16 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

BUILD SUCCESSFUL
Total time: 1 second

おおお。成功したみたいです。(enhanceってなんだろう…)
wwwというフォルダができていて、その中にコンパイルされたクラスと設定ファイルとhtmlファイルがwar形式で収まっていました。

サーバー起動

antからサーバーの起動もできるみたいです。

ant runserver
runserver
Buildfile: build.xml

BUILD FAILED
Target "runserver" does not exist in the project "myproject".

Total time: 0 seconds

あれ…?
失敗してしまいました。


build.xmlの中を見たら確かに「runserver」というtargetはなくて、代わりに「dev_appserver」という名前のtargetがありました。こっちを使うみたいですね。

ant dev_appserver
Buildfile: build.xml

compile:

enhance:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=31 ms, enhance=0 ms, total=31 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

war:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=15 ms, enhance=0 ms, total=15 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

dev_appserver:
     [java] The server is running at http://localhost:8080/

無事に動きました。


ブラウザでhttp://localhost:8080/を開いてみたらこんな画面が表示されました。

ハローハロー!

Users Service

Using the Users Service - Google App Engine - Google Code
Google App EngineではUserServiceというクラスを使って、現在ログインしているGoogleアカウントの情報を使うことができるみたいです。

UserService userService = UserServiceFactory.getUserService();

UserServiceのインスタンスを取得して、

User user = userService.getCurrentUser(); 

現在のユーザーを取得。簡単ですね。


Javadoc

ログインユーザーのニックネームやEmailが取得できるようです。「AuthDomain」ってのはなんだろう…


試しにやってみます。

package org.example;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

public class HelloAppEngineServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        if (user != null) {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello, " + user.getNickname());
            resp.getWriter().println("  Email: " + user.getEmail());
            resp.getWriter().println("  AuthDomain: " + user.getAuthDomain());
        } else {
            resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
        }
    }
}
HTTP ERROR: 500

com/google/appengine/api/users/UserServiceFactory

RequestURI=/helloappengine
Caused by:

java.lang.NoClassDefFoundError: com/google/appengine/api/users/UserServiceFactory

ギャー!


Google App Engineが提供するクラスを使う場合は、src/WEB-INF/libフォルダにjarファイルを入れておかないといけないようです。
「lib/user/appengine-api-1.0-sdk-1.2.0.jar」をプロジェクトフォルダの「src/WEB-INF/lib」にコピーしたらちゃんと動きました。

http://localhost:8080/helloappengine

ログインページにリダイレクトされます。
パスワードを入れる欄がないのはローカルの開発環境だからなのかな?

ログインしたらニックネームとメールアドレスが表示されました。
ローカルなので上手くいってるのかどうかわかりませんが、きっと大丈夫でしょう。

アップロード

夜も遅くなってきましたが、せっかくなのでアップロードして公開するところまでやりたいです。
データストアは飛ばして、アプリケーションのアップロードをやってみます。
うまくいけば***.appspot.comのドメインで、自分の書いたJavaアプリケーションが世界中に公開されるわけです。
楽しみだー。


・・・

cd C:\work\download\appengine-java-sdk-1.2.0
bin\appcfg.cmd update workspace\java-helloworld\www
Reading application configuration data...
Beginning server interaction for java-helloworld...
0% Creating staging directory
5% Scanning for jsp files.
20% Scanning files on local disk.
25% Initiating update.
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=java-helloworld&version=1&
400 Bad Request
Invalid runtime specified.

Unable to upload app: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=java-helloworld&version=1&
400 Bad Request
Invalid runtime specified.

Please see the logs [C:\DOCUME~1\m_ishida\LOCALS~1\Temp\appcfg7220633167556510812.log] for further information.

ぎゃー!


「Invalid runtime specified」
「Javaのランタイムが不正です」ってことなのかな。

うーん。わからない。残念だ。寝よう。

追記

他にもアップロードできない人がいました。

もう少し待てばできるようになるのかなー。

さらに追記

一晩寝て起きたらGoogleからメールが届いていて、全文英語で書かれていたので何がなんだかわからなかったのですが、試しにアップロードしてみたら成功しました!

http://java-helloworld.appspot.com/

やったー!
コメントとかブコメで色々と教えてくれた人たち、ありがとうございます。