GAE/J + Slim3 + Eclipse + 独自ドメイン + Bootstrap でWebサイトを作るメモ

このサイトを設立するにあたり調べたことのメモ。


  • 2016/09/18 作成
  • 2016/09/18 微修正
  • 2016/12/23 追記

Contents


概略

GAE/J + Slim3 + Eclipse + 独自ドメイン + Bootstrap でWebサイトを作るメモ。

GAE側の準備

GAE (Google App Engine) ってのは最近流行りのPaaS的なやつ。けっこう制限が多いけど難しい設定をしなくても動くから、自鯖用意するのメンドクセーって人には使い道が結構あったりする。無料でもそこそこ動いてくれるのも大きい。

レンタルサーバにスペースを用意するのと同じように、GAEのプロジェクトを作成する。

Eclipse + Slim3 の環境を作る

普通のテキストエディタを使ってコンソールからゴニョゴニョしてデプロイするのが好きって人もいるかもだけど、小生のようなGUI人間にはIDEの方が100倍使いやすいのでこっちを使う。

Eclipseを入手する

EclipseはIDEの1つ。C#でいうところのVisual Studioみたいなやつ。無料のIDEとしては一番といっていいくらい有名。

Eclipseには沢山のバージョンがあって、動作が軽快だからと言って古いバージョンが活躍していることも多い。また最新バージョンだけ見ても用途に合わせて様々なパッケージが用意されているので、適当に選ぶと後で苦労する。今回使うのは「Eclipse Java EE IDE for Web Developers」。以下のページから入手できる。

ページ右にある「Download Links」から環境に合ったものを選ぶ。好きなところに展開したら終了。ワークスペースは適当に設定しておきましょう。起動直後の「Welcome」タブは閉じてしまっておk。

プラグインの準備

GAE用のSDKをインストールする。さらにEclipseから使うためにプラグインをインストールする。少し記述が古い記事だけど、以下のページあたりを見て作業を進めましょう。

いろいろインストールさせられるかもしれないけれど、自己判断で必要そうなものは入れましょう。

Java実行環境の準備

先に言っておくと、JavaはWindows10(64bit)なら「jdk-7u80-windows-x64」を入れましょう。何も考えずに今手元にあるJava環境を使おうとするとしくじります。

バージョンがぐちゃぐちゃだと、以下で説明されているようなエラーに苦しめられることになります。小生は苦しめられました。だからちゃんと1.7入れましょうねってことです。

で、そのjdk1.7の入手がちょっと面倒。もしかしたら「Oracleプロファイル」なるもののアカウントを作る羽目になるかも。でも素直に従っておきましょう。

Eclipseの主な設定は以下のページの下のほうを見るとわかりやすいかも。Slim3の話はフライングなので後で見ておきましょう。でも実はあまり役には立たない事情があったり(後述)。

で、実はこれだけの設定だと後で困る。以下のページに従ってさらに設定をしましょう。

つまり、

  1. jdk-7u80-windows-x64.exe を起動、インストール先を適宜指定してインストールする。
  2. 「Window→Preferences」から設定ウィンドウを開き、以下の設定をする。
    • コンパイラー準拠レベルを変更する。「Java→Compiler」を開き「JDK Compliance」欄の「Compiler compliance level」を「1.7」に変更する。
    • インストール済みJAVAを設定する。「Java→Installed JREs」を選択、ここでAddボタンから「jdk1.7.0_80」フォルダをリストに追加し、チェックボックスをオンする。
  3. eclipse.iniを開いて以下の通り編集する。
    • Javaのバージョン指定を1.7にしておく。「-Dosgi.requiredJavaVersion=1.7」ってな感じで。でも実は1.8でも普通に動く。
    • vmを指定する。1.7のjavaw.exeを指定するとeclipseが起動しなくなる。仕方がないのでjdk1.8.0を別途インストールして、そのvmを指定する。具体的には「-vmargs」の上あたりに「-vm(改行)C:\Program Files\Java\jdk1.8.0_102\bin\javaw.exe」のように記述する。区切り文字は普通に「\」(円マーク)でおk。
      • vmを指定しないと後でデプロイできなくなる。

という感じの設定を一通りこなす。

Slim3の準備

Slim3ってのはGAE/Jに特化したMVCフレームワーク。RubyでいうところのRuby on Rails、PHPでいうところのCakePHPといった感じ。

Slim3の準備だけは他のサイトとちょっと違う。そこら辺のサイトで紹介されている方法に従ってEclipseのプラグインを入れようと思っても、Slim3 はGoogleCodeからGitHubへの移行がされないままsvnが使えない状態になっているため、http://slim3.googlecode.com/svn/updates/ を入力しても404なんですよね。

というわけで、以下のページからslim3-blank-1.0.16.zipを自分でダウンロード。

ざっくりとした設定は以下の通りに進める。

つまり、

  1. zipを展開すると出てくる「slim3-blank」フォルダがそのままblankプロジェクトになってる。これを複製して適当なフォルダ名にして中身をいじくることでプロジェクトを作っていく。
  2. そのプロジェクトをインポートするときは「File→Import」を選択して出てきたウィンドウで「General→Existing Projects into Workspace」を選択、Nextを押して「Serect root directory」の部分でさっきのプロジェクトを選択する。Finishを押せばめでたくインポート完了。
  3. プロジェクトエクスプローラに表示されているルートの名前が「slim3-blank」のままだったら、右クリックから「Refactor→Rename...」を選択、適宜リネームする。
  4. 「Window→Preferences」から設定ウィンドウを開き、以下の設定をする。
    • Java→Code Style→Organize Importsの「Number of static imports needed for .*」を「1」にする。
    • Java→Editor→Content Assist→Favoritesを選択し、「New Type...」からリストに以下の項目を追加する。
      • org.hamcrest.CoreMatchers
      • org.junit.Assert
      • org.junit.matchers.JUnitMatchers
  5. プロジェクトエクスプローラでルートを右クリックしてPropertiesを開き、以下の設定を行う。
    • Resourcesで「Text file encoding」を「UTF-8」にする。
    • Java Compilerで「Compiler compliance level」を「1.7」にする。(ここは適当でも割とどうにかなる。)
    • Java Compiler→Annotation Processingの「Generated source directory」が「src」になっているか確認する。なっていなければ設定しなおす。
    • Java Compiler→Annotation Processing→Factory Pathで表示されるリストで「/(プロジェクト名)/lib/slim3-gen-1.0.16.jar」にチェックが付いているか確認する。付いていない・リストに載っていない場合は適宜設定する。

ちなみに、はるか昔に環境を準備したことがあってEclipseにプラグインが既に入ってる人は以下を参考にするといいかも?

コントローラを作る

Eclipseを使ったことがある人ならコントローラ類は完全に自作するのかもしれないが、Slim3の場合は半自動でその辺をやってくれる機能が付いている。

例えば「/hello/」というパスのページのコントローラ・ビューを作成したい場合は以下の手順を踏む。

  1. プロジェクトエクスプローラに表示されている「build.xml」を右クリックし、「Run As」→「Ant Build...」を選択する。「...」が入ってる方を選択する。
  2. 「Edit Configuration」ウィンドウが表示されたら、そのまま「Run」をクリック。
  3. さらに「Input a controller path.」と書かれたダイアログが表示されたら、「/hello/」と入力する。ここは自分が作りたいページの名前にする。
  4. プロジェクトエクスプローラを開いて以下を確認する。
    • コントローラとして「/(プロジェクト名)/src/slim3.controller.hello/IndexController.java」が作成されている。
    • ビューとして「/(プロジェクト名)/war/hello/index.jsp」が作成されている。

あとは適宜これらのファイルを編集したり、新たなファイルを作ったり何だかんだしてプロジェクトを編集する。

動作のテストをする

上のように「/hello/」だけ作った状態のプロジェクトなら、以下のようにして動作を確かめられる。

  1. (念のため)プロジェクトエクスプローラ上のルートで右クリックし、Refreshを選択。
  2. (念のため)Project→Clean...から古いビルドを消す。
  3. プロジェクトエクスプローラ上のルートで右クリックし、「Run As」→「4 Web Application」を選択。グーグルのアイコンっぽいのが表示されているのでわかるはず。
  4. コンソールに赤い文字列がつらつらと流れる。特にエラーが出ずに「Dev App Server is now running」が表示されたら成功。
  5. Webブラウザで「http://localhost:8888/hello/」を開くと、さっきのindex.jspの中身がそのまま表示される。エラーが出たらそれに従って試行錯誤する。

プログラムを作るときの注意点

外部jarの読み込み

適当なライブラリを読み込ませたいという場面はとても多いだろうが、適当に設定するとすごく困る。なのできちんと手順を知ったうえで設定しましょう。その手順というのが以下。

  1. 読み込みたいjarファイルは「/(プロジェクト名)/war/WEB-INF/lib/」以下に置く。←これが特に重要
  2. プロジェクトエクスプローラ上のルートで開く右クリックメニューからRefreshすることでプロジェクトエクスプローラにさっきのjarが表示されるようになる。
  3. ビルドパスの設定をする。プロジェクトの設定の「Java Build Path」で「Libraries」で「Add JARs...」からさっきのjarを選択してリストに追加する。

よくある間違いとして、jarを入れる場所が違うというのがある。このとき具体的には以下のような状態に陥ってしまう。

  • 読み込みたいjarファイルは「/(プロジェクト名)/lib/」以下に置いてある。←これがマズい
  • ビルドパスの設定は済んでいる。
  • エディタで表示しているソースには特にエラー表示はない。
  • でも動作を確かめると「NoClassDefFoundError」になる。

このとき、Eclipseからはjarが見えているがアプリからは見えていない、という状態らしい。

普通、ライブラリの読み込みが失敗していればソースの編集画面でエラーが出る。それが出ないのに動かしてみると動かないということで混乱する。

デプロイ

GAE側でプロジェクトを既に作成してあるなら、デプロイ前に以下の設定をしておく。

  1. プロジェクトエクスプローラのルートで右クリックし、「Google」→「App Engine Settings...」を選択。
  2. 出てきたウィンドウの「Deployment」欄の「Project ID」にGAE側で設定したプロジェクトIDを入力し、「Version」に適当な数字(例えば「1」とか)を入力する。後でロールバックするときにこの番号が基準になる。

デプロイするときは以下の手順で行う。

  1. プロジェクトエクスプローラのルートで右クリックし、「Google」→「Deploy to App Engine」を選択。
  2. 「Deploy Projects to Google App Engine」というダイアログが表示される。そのままDeploy。
  3. ダイアログが出てきてプログレスバーが表示される。コンソールにデプロイの進捗状況も表示される。エラーが出なければデプロイ成功。エラーダイアログが出たらその内容に従って修正。
    • Googleのログイン画面が出たらログインする。
    • アクセスを許可するか的なやつが出たら許可しておく。

もう少し細かく知りたい場合、これを見るといいかも。

エラーが出てデプロイに失敗した場合、以下を参考にするといいかも。「Unable to update app: Cannot get the System Jave Compiler. Please use a JDK, not a JRE.」と言われたら、eclipse.iniの設定を見直しましょう。

独自ドメインを使いたい場合

appspot.comってなんだか長ったらしい、もっと短いのがいいと思う人は結構いるはず。.tkのような無料で使えるドメインなら気軽に試すことができる。

ただし、DNSサーバの設定が別途必要になるので以下も併用する。TXTレコードとかAAAAレコードが使えないと後で困るので。

手順は以下の通り。

  1. Dot TKから.tkドメインを取得する。アカウントの作成を求められたりしたら素直に従う。
    • 登録時のDNSの設定は適当でも大丈夫。一応、以下のように入れておけば安全。
      • プライマリ・ネームサーバ…ホスト名「ns1.dzndns.com」、IPアドレス「175.41.239.64
      • セカンダリ・ネームサーバ…ホスト名「ns2.dzndns.com」、IPアドレス「174.129.215.194
    • 上の入力例は以下のページに載ってたやつの丸パクリ。
  2. Dozensにアカウント作成し、ドメインの設定を行う。
    • このとき、DozensのNameServerを確認する。ns1.dzndns.comns4.dzndns.comの4つが表示されているはず。
  3. .tkの管理画面から以下の設定を行う。
    • URL Forwardingの設定を消す。
    • 「Management Tools」→「NameServers」を開き「Use custom nameservers (enter below)」を選択、入力欄にDozensのNameServerを4つ全部入力する。「Change Nameservers」をクリックすれば設定が反映される。

.tkとDozens関係の設定については以下のページが参考になる。GAEじゃないけど。

次にGAEとDNS関係の設定を行う。

  1. 以下のページを開き、「GO TO THE CUSTOM DOMAINS PAGE」ボタンをクリック。
  2. 出てきたページの指示に従って設定をする。というか上のページに殆ど書いてある。具体的には
    • 所有者の確認のためにTXTレコードを追加する。Dozensの方で設定が完了しても、Googleの方で反映されるまで数時間かかるのでただただ待つ。暇を見つけてリトライしまくる。
    • それが済むと、AレコードやらAAAAレコードやらの設定を求められるのでDozensの方で設定する。無料枠だと12レコードまでなので、数が多くて溢れるようなら不要なレコードは消しておk。
  3. 何やかんや設定して、独自ドメインの方からappspot.comと同じ内容が見られるようになったら設定完了。

Slim3特有のコーディング

基本的には以下のドキュメントを読んでおいてください、って感じだけど個人的重要点はここにもメモっておく。

パラメータの取得

いつも以下のように書いているところは

String parameter = (String) request.getAttribute("parameter");

以下のように書けばおk。他にもasXxx系のやつが沢山あるので適宜使い分ける。

String parameter = this.asString("parameter");

パラメータの設定

いつも以下のように書いているところは

request.setAttribute("message", message);

以下のように書けばおk。他にもxxxScopeがあるので使い分ける。

this.requestScope("message", message);

URLマッピング

普通のレンタル鯖だとmod_rewriteを使うことになるが、GAEでは使えない。じゃあどうするのか。

Slim3に拘らない人→Url Rewrite Filterでmod_rewriteっぽく

新しくjarをビルドパスに追加したりweb.xmlを編集したり、面倒といえば面倒だけどjavaのコードは1文字も書かないで済む。

Slim3の機能を使う人→AppRouter

以下のマニュアルページを読めば大体わかるかと。

ただし、細かいやり方は書いてないのでわからなければ次のようにしましょう。

  1. プロジェクトエクスプローラ上の「src」を右クリックし、New→Classを選択。
  2. 出てきたウィンドウで以下のように設定する。
    • Package: slim3.controller (Browse...ボタンから選択する)
    • Name: AppRouter
    • Superclass: org.slim3.controller.router.RouterImpl
  3. 「Finish」を押せば[/(プロジェクト名)/src/slim3.controller/AppRouter.java]ができる。

細かい書き方はマニュアル参照。

静的ファイルの扱い

普通のGAEプロジェクトならapp.yamlにいろいろ記述するらしいが、Slim3を使うなら/(プロジェクト名)/war/WEB-INF/appengine-web.xmlにいろいろ書き加える。

favicon.icoを/(プロジェクト名)/war直下に置くのであれば以下のように書けばおk。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
	<static-files>
		<include path="favicon.ico" />
	</static-files>
</appengine-web-app>

…と、これで終わってもいいのだが、AppRouterisStaticを自分で書き直すことでもっと細かく制御できる。詳細はMediawiki記法の文法を参照。

Error 404 をもっと親切に

へんてこりんなリクエストが飛んできたとき、デフォルトでは「Error 404」とだけ書かれた素っ気無いページが表示される。これはあまりよろしくない。もっと自分好みにしたい。

ローカル環境だと動かない(今まで通りの素っ気無いページしか出ない)からビビる。でもデプロイしたら動くので、デプロイすれば動作を確認することができる。逆に言えば、デプロイしないと動作を確かめられない。やばい。

web.xmlをきちんと設定したらエラーページの表示はできるが、404なのか500なのか、どう判別したらよいのか。少し調べたらこんなことが。

エラー用のJSPファイルのヘッダにて、
<%@ page isErrorPage="true" %>
とすることで、JSP内で 暗黙オブジェクトである exception が扱えるようになります。
exceptionオブジェクトには、サーブレットやJSPで発生した例外が入っていますので、例外クラスによって表示を切り替えるのも可能でしょう。

ただし、exceptionで拾うのはサーブレットやJSPなどJava内で発生した例外を扱うものです。
具体的な例を挙げると、HTTPレスポンスコード404のようにリソースが存在しないURLを指定した場合にはサーブレットが実行されませんので適しません。

あれ、ダメなの!?

ということでもう少し調べたらやっぱりできた。

HttpServletRequest オブジェクトとHttpServletResponse オブジェクトによってエラー情報にアクセスできるので、役に立つデバッグ情報や目的の例外ハンドラを生成できます。

次のコード例では、request オブジェクトからエラー属性を取得し、スタックトレースを含めてそれらをブラウザで表示します。

...
Object status_code = 
    req.getAttribute("javax.servlet.error.status_code");
Object message =
    req.getAttribute("javax.servlet.error.message");
Object error_type = 
    req.getAttribute("javax.servlet.error.exception_type");
Throwable throwable = (Throwable) 
    req.getAttribute("javax.servlet.error.exception");
Object request_uri = 
    req.getAttribute("javax.servlet.error.request_uri");

out.println("<HTML><BODY>");
out.println("<B>ステータスコード:</B> " + status_code.toString());
out.println("<BR><B> メッセージ</B>:" + message.toString());
out.println("<BR><B>エラータイプ</B>:" + error_type.toString());
out.println("<BR><B>リクエスト URI</B>:" + request_uri.toString());
out.println("<HR><PRE>");
if (throwable != null) {
  throwable.printStackTrace(out);
}
...

おまけ

Wiki記法をHTMLに変換する

せっかくなのでHTML直書きはしたくない、ということでmediawiki記法をHTMLに変換できるライブラリを使う。

同様の機能を有するライブラリが複数あるらしく、どれを使えばよいか迷った。

結局、上の記事の人も使っているgwtwikiを使うことにした。

ただし使い方については詳しく書いてなかったので他で調べた。${image}とか${title}を使えばリンクのベースとなるURLを指定できる。

WikiModel model = new WikiModel("./img/${image}", "./${title}");
model.setUp();
String html = model.render(text);

Mediawiki記法の文法概要

記法は以下の早見表を見るとわかりやすい。

詳細は下のページから該当する項目へ飛ぶと結構細かく書いてある。

こっちも参考になる。WikipediaだけどMediawikiと同じだから、まあ大丈夫でしょう。

リンクについて

リンクに用いるテキストに「[」や「]」を入れたい場合はそれぞれ「&#91;」「&#93;」と入力することでリンクの開始・終了と誤認されずに済む。

あと、リンクが英字だと先頭の字が勝手に大文字になる。具体的には[[memo_page|めも]]と書くとMemo_pageにリンクされてしまう。もしmemo_pageにリンクさせたいのであれば[[./memo_page|めも]]のように書くことで回避できる。

構文ハイライト

プログラムのコードはSyntaxHighlightを使うとハイライトしてくれる。下のリンクを辿ると対応言語一覧も出てくる。javacsharp、といった感じでlangを指定する。

画像の貼り付け・画像リンクの処理

岩倉市の一般住宅 (aiueo700 - アンサイクロペディア より)
岩倉市の一般住宅 (aiueo700 - アンサイクロペディア より)

画像も簡単に貼ることができる。たとえば右の画像は[[File:The_Memory_of_The_Summer.jpg|thumb|岩倉市の一般住宅]]のように記述することで表示可能。cssを少し弄ればWikiediaっぽい雰囲気を演出できる。詳細は以下を参照のこと。

ただしSlim3との同時使用だと画像をクリックした後の遷移先である「File:~」のURIは自動的に静的ファイル扱いされてしまうため404となる。これを回避するために、またもやAppRouterの設定を弄る。

Slim3の中では以下のような順序でリクエストの処理が進むらしい。

  1. 静的ファイルへのリクエストかどうかisStaticで判定する
  2. 判定結果がtrueの場合
    • そのままのURLで静的ファイルへのリクエストとして処理する
  3. 判定結果がfalseの場合
    • まずRoutingの設定が適用され、パスが書き換えられる
    • その上で各コントローラに処理が割り振られる
    • 書き換え後のパスが静的ファイルとして認識可能な場合はそのパスで静的ファイルへのリクエストとして処理する

デフォルトでは拡張子つきのファイル名ぽい文字列がURLの最後に含まれていると静的ファイル扱いされてしまい、ルーティングの設定が適用されない。AppRouterで記述したルーティングの設定を適用させるためにはisStaticをOverrideし、静的ファイルかどうかの判定を自分で1から書く必要がある。

package slim3.controller;

import org.slim3.controller.router.RouterImpl;

public class AppRouter extends RouterImpl {
    public AppRouter() {
        // これだけではダメ
        addRouting("/memo/File:*param", "/memo/img/{param}");
    }

    @Override
    public boolean isStatic(String path) throws NullPointerException {
        if (path.contains("/File:")) {
            return false; // wikiページから画像へのリンク→動的
        } else {
            return super.isStatic(path);
        }
    }
}

ファイル全体をStringとして読み込む

GAEではPathが使えないのでapache commons-ioのFileUtils.readFileToStringを使う。

これだけでStringに読み込める。

String s = FileUtils.readFileToString(filename, "utf-8");

ファイル名の大文字・小文字

手元のみで走らせて試験した限りでは、大文字と小文字は区別されなかった。つまり、ファイル名がmyfile.txtだった場合、Myfile.txtと指定しても読み込めた。

// 以下で"myfile.txt"が読み込まれる
String s = FileUtils.readFileToString("Myfile.txt", "utf-8");

ところが、GAEにデプロイして動作を確かめると今度は大文字と小文字が区別されるらしく、うっかりしているとファイルが読み込めない。

// "myfile.txt"は存在するが"Myfile.txt"は存在しない→java.io.FileNotFoundException
String s = FileUtils.readFileToString("Myfile.txt", "utf-8");

といわけで大文字と小文字は区別しましょう。

java.lang.RuntimeException

eclipseが異常終了すると、再起動後にデプロイなどの操作ができなくなってしまうことがある。コンソールには「java.lang.RuntimeException: Unable to locate the App Engine agent. Please use dev_appserver, KickStart, or set the jvm flag:...」とか、「An internal error occurred during: Deploying XXX to Google java.lang.NullPointerException」のように表示される。

応急処置として、メニューから「Run」→「Run Configurations」を選択して開いたウィンドウで、該当プロジェクトが選択された状態で「Arguments」タブを選択し、「VM arguments」の入力欄に「-javaagent:C:\path-to-your-eclipse\plugins\com.google.appengine.eclipse.sdkbundle_1.9.34\appengine-java-sdk-1.9.34\lib\appengine-agent.jar」と入力し、「Apply」を押した後に続けて「Run」を押すとローカルサーバの動作だけはするようになる。

でも、これだと毎回入力しなおさないと動作しないし、デプロイもできない。ほかの解決策も書かれているが、ビルドパスの設定も特に間違っているということはなさそうだし、もうどうすればいいのかお手上げ状態になってしまった。

そんなことで調べていたら、出てきました。解決方法。

パッケージエクスプローラ上の「App Engine SDK [App Engine – 1.x.x]を右クリックし、ビルドパスから除去して、プロジェクトのプロパティから再度SDKを指定すれば自動で入りました。

パッケージエクスプローラが表示されていない場合は、メニューから「Window」→「Show View」→「Navigator」を選択すると表示できる。