Google App Engine/Javaにcronの設定を入れてみた。

デプロイしたら以下エラー

Unable to upload:
java.io.IOException: Error posting to URL: http://appengine.google.com/api/datastore/cron/update?app_id=*****&version=*&
500 Internal Server Error

Server Error (500)
A server error has occurred.

	at com.google.appengine.tools.admin.ServerConnection.send(ServerConnection.java:140)
	at com.google.appengine.tools.admin.ServerConnection.post(ServerConnection.java:78)
	at com.google.appengine.tools.admin.AppVersionUpload.send(AppVersionUpload.java:345)
	at com.google.appengine.tools.admin.AppVersionUpload.updateCron(AppVersionUpload.java:101)
	at com.google.appengine.tools.admin.AppVersionUpload.doUpload(AppVersionUpload.java:87)
	at com.google.appengine.tools.admin.AppAdminImpl.update(AppAdminImpl.java:49)
	at com.google.appengine.eclipse.core.proxy.AppEngineBridgeImpl.deploy(AppEngineBridgeImpl.java:271)
	at com.google.appengine.eclipse.core.deploy.DeployProjectJob.runInWorkspace(DeployProjectJob.java:148)
	at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:38)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:55)


どうやら、cron.xmlに日本語は使えないようです。
エラーからはいまいちよくわからなかったけど、日本語部分を削除したら通った。


毎週木曜の19:00にやるならこんな感じ

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
  <cron>
    <url>/cron/hoge</url>
    <description>thursday 19:30</description>
    <schedule>every thursday 19:30</schedule>
    <timezone>Asia/Tokyo</timezone>
  </cron>
</cronentries>

timezoneを指定すれば、UTCで指定しなくてもすむ。


上記を見ればわかるように、cronは基本的にURLをたたくだけなので、
サーブレット(doGet)をつくり、マッピングをする。
その際セキュリティ保護しても平気だよ。とのことなので、以下のようにweb.xmlに追記


<servlet-mapping>
	<servlet-name>numberCheck</servlet-name>
	<url-pattern>/cron/hoge</url-pattern>
</servlet-mapping>

<security-constraint>
	<web-resource-collection>
		<web-resource-name>cronJobs</web-resource-name>
		<url-pattern>/cron/*</url-pattern>
	</web-resource-collection>
	<auth-constraint>
		<role-name>admin</role-name>
	</auth-constraint>
</security-constraint>


上記のように指定してみたところ、ログに
エラーも、実行ログも出ない。
cronjob自体は失敗となる。


で、requests onlyでログを見ると

      05-21 09:46PM 26.372 /cron/hoge 302 2002ms 1582cpu_ms 0kb
      See details

      0.1.0.1 - - [21/May/2009:21:46:28 -0700] "GET /cron/hoge HTTP/1.1" 302 243 - -


302ですか・・・
直接サーブレットたたくと、ログインページにリダイレクトされるし。。。
cronでもそうなってるっぽいよね


というわけで、
セキュリティ保護をはずして実行したところ、無事成功。


一応以下のようにユーザ情報を取ってみました。

		UserService userService = UserServiceFactory.getUserService();
		Principal principal = req.getUserPrincipal();
		log.info(principal == null ? "principal is null" : principal.getName());
		User currentUser = userService.getCurrentUser();
		log.info(currentUser == null ? "no user" : currentUser.getNickname());
		String header = req.getHeader("X-AppEngine-Cron");
		log.info(header);


結果

principal is null
no user
true

だそうで、やはりユーザがらみが駄目そう。一応cron起動だというヘッダは入っているといった感じですね。
ひとまずはセキュリティ保護を自前にして対応しますかね

追記

更新がまだだったら、5分スリープして再度リトライとかいうコードはかけないんですね・・・
しょうがないので、同じようなcron設定を複数入れました。
urlの重複とかも駄目っぽいので適当にクエリを付与して重複しないようにしました。


後続のcronは前回の結果を見るようにしました。


こんなので正しいのかなぁ・・・


なんとなく、処理自体を別スレッドにして放り投げてしまえばバックエンドでちゃくちゃくとやってくれそうな気もしますが、
EntityManagerはfilterで閉じてしまうので、やめました。
もっとも、filterで開いたEntityManagerを使わずに改めてEntityManager利用すればいいだけかとも思いますが・・・