hakobera's blog

技術メモ。たまに雑談

Play framework の writeChunk で UTF-8 以外の文字列を送信するとレスポンスが止まってしまう問題の回避策

2012/7/9 追記 この問題は Playframework 1.2.5 で修正されました。以下は 1.2.4 以下を使う場合にのみ適用して下さい。

最近、仕事で Play framework を本格的に使い始めて、結構惚れ込んでおります。
で、既存のアプリをどんどん Play で書きなおしているんですが、CSV ファイルの文字エンコーディングの所で躓いたので、共有しておきます。

Play で大容量のデータをストリームでダウンロードさせるには response.writeChunk() メソッドが便利です。しかし、このメソッド、response.encoding を UTF-8 以外にすると止まってしまいます。(最初の chunk を送信する所で止まってしまう)

世の中には「Excel で読める CSV ファイルを出力する」というお仕事が多々あるわけで、ようするに MS932 でレスポンスを送信しなければならないので、この状態だと困ってしまいます。

調べていくと、PlayHandler.java の LazyChunkedInput#nextChunk() メソッドでレスポンスを書き出す所で String#getByte() を引数なしで呼出し = 強制的に UTF-8 で出力、となってしまっているからでした。

ソースの該当箇所としてはここ ( LazyChunkedInput#nextChunk() )
https://github.com/playframework/play/blob/1.2.x/framework/src/play/server/PlayHandler.java#L1014

このメソッドを以下のように修正して、Play をビルドしなおせばOK。

public Object nextChunk() throws Exception {
    if (nextChunks.isEmpty()) {
        return null;
    }
    return wrappedBuffer(((String) nextChunks.poll()).getBytes(Response.current().encoding));
}

同じクラスの writeChunk() メソッドの方ではエンコーディンを意識しているのでバグ(考慮漏れ)だと思います。ここで設定しているデータサイズと、nextChunk で送信しているデータサイズがずれるのが、転送が止まってしまう原因だと思われます。

public void writeChunk(Object chunk) throws Exception {
    String message = chunk == null ? "" : chunk.toString();
    StringWriter writer = new StringWriter();
    // ここではエンコーディングを見ている
    Integer l = message.getBytes(Response.current().encoding).length + 2;
    writer.append(Integer.toHexString(l)).append("\r\n").append(message).append("\r\n\r\n");
    nextChunks.offer(writer.toString());
}

Play 使っている人は MS932 の呪縛から逃れられている人も多いと思いますが、参考になれば幸いです。もう少し使ってみて問題がなさそうなら、pull リクエストを送ってみようと思います。

1/7 追記
pull リクエスト送ってみました。
[#1363] Fix reponse.writeChunk() cannot send multibyte string data when response.encoding is not UTF-8 by hakobera · Pull Request #430 · playframework/play · GitHub