Google App Engine を使ってみよう(2) 〜 セッションを使う

前回作った掲示板では投稿後にすぐに一覧画面にリダイレクトするため、投稿完了メッセージを表示できなかった。
POST 後にメッセージを表示するための方法としては、セッションを使う方法がある。

というわけで、今回はセッションを使ってみる。



まず web.xml を編集してセッションを有効にする。

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


続いて、処理完了時に投稿処理用コントローラでセッションに投稿済みフラグをセットする。


PostController.java

	@Override
	public Navigation run() throws Exception {
		
		if(isPost() && doPost()) {
			// 追加
			sessionScope(PostConstants.IS_POSTED, true);
			return redirect("/");
		}
		
		return forward("post.jsp");
	}


PostControllerTest.java

	@Test
	public void run() throws Exception {
		// 略
		Posting stored = Datastore.query(Posting.class).asSingle();
		assertThat(stored, is(nullValue()));

		// 追加
		assertThat(tester.sessionScope(PostConstants.IS_POSTED), is(nullValue()));
	}

	@Test
	public void addNewPost() throws Exception {
		// 略
		assertThat(stored.getContent(), is(content));
		assertThat((new Date().getTime() - stored.getPostingDate().getTime()) <= 1 * 1000, is(true));

		// 追加
		assertThat((Boolean) tester.sessionScope(PostConstants.IS_POSTED), is(true));
	}


PostConstants.java

public class PostConstants {
	public static final String IS_POSTED = "isPosted";
}


リダイレクト先のコントローラでフラグをセッションからリクエストスコープに引き渡し、jsp 内で出力させる。
ただし、メッセージの引渡し後もセッションをそのままにしておくと毎回メッセージが表示されてしまうので、1 回だけ表示させるために null で上書きする。


controller.IndexController.java

	@Override
	public Navigation run() throws Exception {
		
		List<Posting> postingList = service.getPostingList();
		requestScope("postingList", postingList);
		
		// 追加
		requestScope(PostConstants.IS_POSTED, sessionScope(PostConstants.IS_POSTED));
		sessionScope(PostConstants.IS_POSTED, null);
		
		return forward("index.jsp");
	}


controller.IndexControllerTest.java

	// 追加
	@Test
	public void afterPosting() throws Exception {
		tester.sessionScope(PostConstants.IS_POSTED, true);
		tester.start("/");

		IndexController controller = tester.getController();
		assertThat(controller, is(notNullValue()));
		assertThat(tester.isRedirect(), is(false));
		assertThat(tester.getDestinationPath(), is("/index.jsp"));

		assertThat(tester.requestScope("postingList"), is(notNullValue()));
		assertThat((Boolean) tester.requestScope(PostConstants.IS_POSTED), is(true));

		tester.start("/");
		assertThat(tester.requestScope(PostConstants.IS_POSTED), is(nullValue()));
	}


index.jsp

<!-- 追加 -->
<c:if test="${isPosted}"><p>Posting has been completed.</p></c:if>


ここまでで一応動くものはできたけど、このままだと有効期限切れのセッションがたまり続けてクォータを圧迫するので、cron を使って定期的に削除するようにする。
(参考: tdtshのブログ» Google App Engine for Java で セッションの_ah_SESSIONが増え続ける)


まず、web.xml でセッション削除用のサーブレットを登録する。
servlet-name は servletservlet-mapping の対応が取れていれば何でもいいと思う。
cron で定期的にアクセスさせる URL はなんとなく /cron 配下にしておきたかったので、そういう風に設定 & アクセス制限をかけた。


web.xml

    <servlet>
        <servlet-name>_ah_sessioncleanup</servlet-name>
        <servlet-class>com.google.apphosting.utils.servlet.SessionCleanupServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>_ah_sessioncleanup</servlet-name>
        <url-pattern>/cron/_ah/sessioncleanup</url-pattern>
    </servlet-mapping>
    <security-constraint>
        <web-resource-collection>
            <url-pattern>/cron/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>


続いて cron 用の設定ファイル cron.xml を war/WEB-INF に作り、以下の内容を追加する。
URL は web.xmlservlet-mapping で指定した URL を設定する。


war/WEB-INF/cron.xml

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
	<cron>
		<url>/cron/_ah/sessioncleanup?clear</url>
		<description>Clean up sessions</description>
		<schedule>every 12 hours</schedule>
	</cron>
</cronentries>


これでセッションエンティティが増え続けれることはなくなった。
ただ、あらかじめ用意されているセッションを使うよりも、自前でセッション管理した方がいろいろと利点があるらしい。


ソースコードGitHub に上げているので参考にどうぞ。
(今回のソースコードタグ v2 で参照可)