2012年5月29日火曜日

Playframework 2.0でスケジュール

こんな感じ

import play.api.libs.concurrent.Akka
import play.api.Play.current
import akka.util.duration.intToDurationInt

Akka.system.scheduler.schedule(0 seconds, 1 minutes) {
    println("test")
}


第1引数は初期遅延時間、第2引数はインターバルです。
Akkaを直で使っている感があって、どうなんよ?と思わなくもない。

「0 seconds」「1 minutes」という書き方はint型である0や1が暗黙変換関数のintToDurationIntによってDurationInt型に変換され、そこからさらにそのメソッドである「seconds」「minutes」によってDuration型に変換されるというScalaの必殺技。(^^;;;

細かいことを考えなければ便利なんだろうけど、初回使用の際にはどういう変換が行われているのかをちゃんと調べないと気持ち悪いので逆に面倒な気もしないでもない。

ちなみに暗黙変換を使用しない場合は引数は


new FiniteDuration(1, TimeUnit.MINUTES)

のようになって微妙な感じなんでAPI開発者の意図としては常に暗黙変換を使ってね、ってことなんでしょう。

最初のコードはまったくAPIを知らない状態で見てもなんとなく意味がわかるという点で優れていると思うけど、ちゃんと使いこなそうと思ったらちょっとハードル高いかなぁ。。。

書きながら気がついたけど、Playframework側でAkkaをラップしたAPIを用意しようとしても結局はAkka側の暗黙変換関数もimportしないといけないのでそれでこんな形になったのかも。

2012年5月26日土曜日

Playframework 2.0はじめました

半年振りのScala。
大分忘れてる。。。 

それを割り引いても、まだまだ1.2.4の方が2.0.1よりも使い勝手も完成度も高い気がする。
仕事で使うツールとしては当面1系からは乗り換えられないでしょう。

 とりあえずフォームからアップロードされたファイルを扱う場合のコードがPlay1に比べてホンマかいなと思うくらい冗長になることに愕然とした。

 あとはXMLの加工をどうやってやるべきか?ということに迷う。
 一発で作るならXMLリテラルはめちゃめちゃ便利だけど、多くの場合はXMLの組み立ては条件分岐しながら一部ずつ組み立てていくのでその場合どうするのがベターなのかがよくわからない。

 まぁしばらくはPlay2.0で遊んでみるので、ぼちぼちやっていきます。

2012年4月12日木曜日

Playframeworkでコンソール文字化け

Windows + Playframeworkで開発しているともれなく、コマンドプロンプトでの日本語表示が化けると思うんだけど、何故か検索してもまったく情報がひっかからない。
なんでだろ???

皆Windowsで開発していないのかな?

とりあえず回避方法は以下です。

1、System.out(err).printlnの文字化け回避
OnApplicationStartのJobを作成してそこでSystem.outとerrを差し替えます。

@OnApplicationStart
public class ConsoleLog extends Job {

    private boolean consoleSetuped;

    public void doJob() {
        if (!consoleSetuped) {
            try {
                PrintStream ps = new PrintStream(System.out, true, "MS932");
                System.setOut(ps);
                System.setErr(ps);
            } catch (UnsupportedEncodingException e) {
                //not occur
            }
            consoleSetuped = true;
        }
    }
}

2、Loggerでの文字化け回避
log4j.propertiesに

log4j.appender.Console.encoding=MS932

を追加

いじょ。

2012年4月4日水曜日

Playframeworkでダウンロード後にファイル削除

playframeworkでバイナリのダウンロード機能を実装する際にはrenderBinaryを使用する。
この時にダウンロード完了したファイルを削除しようとしていくつか試行錯誤が必要だったのでメモとして残しておく。
やったのは次の順

1、普通にメソッド内のfinallyで削除

public static void download(...)  throws IOException {
    File file = getDownloadFile(...);
    String filename = ...;
    try {
        renderBinary(file, filename);
    } finally {
        file.delete();
    }
}

結果: Fileが見つからない
何故ならrenderXXXXは対応するExcetionをthrowしてそのcatch節で処理を行うから。
catch節での処理が始まるときにはすでにdownloadメソッド自体を抜けており、そのfinally節でのファイル削除が実行されてしまっている。

2、@FinallyとThreadLocalの合わせ技


private static ThreadLocal<File> deleteFile;

@Finally
static void doDeleteFile() {
    File f = deleteFile.get();
    if (f != null) {
         f.delete();
         deleteFile.remove();
    }
}

public static void download(...)  throws IOException {
    File file = getDownloadFile(...);
    String filename = ...;

    deleteFile.set(file);

    renderBinary(file, filename);
}

結果:responseのcontent-lengthが0になる
@FinallyはrenderXXの処理実行後に実行されるので一見うまくいきそうに見える。
しかしRenderBinaryでレスポンスへのwriteが遅延処理されており、やっぱりwriteの前にファイルが削除されてしまう。

3、FileInputStreamのclose時に削除
結局うまくいったのはFileInputStreamのサブクラスを作成してそのclose時にファイルを削除するという方法

class FileInputStreamWithDelete extends FileInputStream {
    
    private File file;
    
    public FileInputStreamWithDelete(File file) throws FileNotFoundException {
        super(file);
        this.file = file;
    }
    
    @Override
    public void close() throws IOException {
        super.close();
        this.file.delete();
    }
}

public static void download(...) throws IOException {
    File file = getDownloadFile(...);
    String filename = ...;
    renderBinary(new FileInputStreamWithDelete(file), filename, file.length());
}

ちょっとひねりが必要な感じですね。

2012年2月29日水曜日

PlayframeworkでMemcachedがタイムアウトする

PlayでMemcachedを使っているとたまに存在するはずのキーでnullが返ってくることがある。
ソース追ったらPlayのMemcachedImplでgetメソッドの実行に1秒以上かかったらそこでタイムアウトするようになってたよ。。。(--

Memcachedはherokuのアドオンをそのまま使用しているのでおそらくアメリカ東海岸にある。
なので、日本のlocalhostからテストすると割とすぐにタイムアウトしちゃうね。。。
東海岸のherokuサーバー上から使う場合は多分タイムアウトすることはないと思うけど。

ちなみにMemcachedImplのタイムアウトコードはハードコードなんで設定を変更することもできない。
対策としては

A. localhostではMemcachedを使わない設定にする
B. 近場に別のMemcachedを立てて、localhostからはそれを使うようにする

の二つが考えられるけど、Aは実行コードが変わるのであまり気が進まないし、Bは面倒くさい。
どうしたもんだろ?

ていうか問題はそこじゃなくて、Cacheを使う際に「存在するはずのキーでnullが返ってくることはありえる」ということをちゃんと認識しておくことなんだよな。

2012年2月16日木曜日

PlayのControllerはJavaじゃない。その1

Playはとても面白いフレームワークだと思う。
ソースもまぁまぁ読んだけど、「やりすぎ。。。」と思うくらい作りこんであるところと「え?こんなあっさりした実装で良いの???」と思う部分のバランスがなんだか不思議な感じ。
久々にソースを読んで感心もしたし、徹頭徹尾ユーザーが「簡単に」Webアプリを作れるように工夫を凝らしていると思う。

そんな中でコントローラは特にやりすぎな方の例でJavaを長くやってきた身からするとそんなんありかよ?と思う部分がかなりある。
ここではそういう部分についていくつか書いておきたい。

まずはその1

staticオブジェクトにアクセスしているのにスレッドセーフ
PlayではHTTPアクションをControllerクラスのサブクラスを作ってそこにstaticメソッドとして定義する。
例えばHelloworldならこんなメソッド定義になる。

public static void hello() {
    renderText("Hello world");
}

とても簡単。
しかし、見ての通りパラメータやセッションなどの情報はどこからも渡されていない。
パラメータについてはメソッドの引数として定義すればそれがそのまま使えたりもするんだけれど、sessionやparamsオブジェクトにはstatic変数を介してアクセスできる。

public static void hello2() {
    String name = params.get("name");
    renderText("Hello " + name);
}

ちょっと待て!って思うよね?
HTTPリクエストなんかはマルチスレッドでパラに動くはずなのにstatic変数なんか使ったらスレッドセーフにならないじゃん。

ところが、これなんとClassLoaderレベルで小細工していてClassロード時にstatic変数をThreadLocalな変数に差し替えている。
つまりコードを見る限り明らかにstatic変数なんだけど、実行時には自動的にThreadLocal変数になっているという荒技。。。(--

もう、この時点でJavaではないと思う。(^^;;;

2012年2月7日火曜日

Herokuのシングルdynoの動作

herokuでは2台以上のdynoを契約すると(つまり課金すると)その上で動いているアプリケーションはidle状態になることなく動き続ける(らしい)けれど、シングルの時には2時間程度リクエストのない状態が続くとアプリは停止し、次にリクエストが来た時にまたスタートアップする仕組みになっています。

逆に言えば定期的にリクエストが来てさえいれば動き続けるわけでherokuアプリ上からcronアドオンなり、独自スレッドなりで1時間に1回程度自分自身に対してHttpRequestを投げてやればidleになることはありません。

監視されているのはHttpRequestだけなので、独自スレッドで定期的にDBへの読み書き処理を行っていてもHttpRequestが来なければ容赦なく落とされます。
夜間バッチなどの処理を組む場合には注意が必要かもしれません。(2台契約しろという話かもしれないけれど、その場合は複数のホストで処理が同時に実行される可能性を考慮せねばならずやっぱり注意が必要)

ちなみに1度idleになったあとに再起動した時にはIPが変わります。というか再起動時には必ずIPが変わっているっぽいです。
スラグのコンパイル&コピーがいつ行われているのかわかりませんが、この辺もスラグサイズをあまり大きくするなという注意事項の要因のひとつでしょうね。