読者です 読者をやめる 読者になる 読者になる

Lambdaカクテル

DESCRIBING: Scala, Akka, P2P, Scheme, Political sth., etc.

Play framework 2.3.xでShift_JISのリクエストを扱う方法

play scala

要点

  • Play framework 2.3.xとUTF-8について解説します
  • Shift_JISでパーセントエンコードされたデータを扱う事の困難性を解説します
  • 実際にPlay framework 2.3.xで上記のデータを受け取る方法について解説します

Play frameworkとUTF-8

Play framework 2.3.x(Scala)は、UTF-8を標準的なcharsetとして扱っている*1。特にパーセントエンコーディングされたデータがGET/POSTメソッドで渡って来るときは、Content-Typeヘッダでcharsetを指定しなければUTF-8であるものとしてデコードされる*2

PlayでShift_JISを扱う困難さ

Shift_JISを扱うようなアプリケーション*3では、フォームの入力などとして渡って来たデータをUTF-8ではなくShift_JISとしてデコードしたい局面が多々ある。しかしながらクライアントの行儀が悪いためにContent-Types: application/x-www-form-urlencoded; charset=Shift_JISのようにきちんとcharsetを指定せずにContent-Types: application/x-www-form-urlencodedとだけ記載されたデータがPlayに渡ってくる。例えば、ほとんどの2ちゃんねるブラウザ(以下、「専ブラ」)はcharsetを指定せずにデータを送信してくる。

Charsetを指定せずにPlayにShift_JISでパーセントエンコーディングされたデータを渡したらどうなるか。やはりUTF-8としてデコードされ、無残にも?????のような文字列となってコントローラに渡ってくる。コントローラに到達した時点でフォームパラメータは、パーセントエンコーディングが規定の(ここではUTF-8)charsetでデコードされ、Map[String, Seq[String]]としてパースされた状態となっている。

またデコードは不可逆であり、一度UTF-8でデコードして得られる文字列をArray[Byte]に型変換してからもう一度Shift_JISとしてデコードすることはできない。

このため、charsetを指定しないクライアントからShift_JISでフォームが送信された場合、これを正しくデコードするのは困難だ。正しく処理するために採ることができる選択肢は:

  1. Play frameworkのソースを修正してリビルドする
  2. パーセントエンコーディングのパーサーにかかる前にリクエストのヘッダを修正する
  3. 生の文字列としてリクエストを受け取り、手動でShift_JISとしてパースする

1: Play frameworkのソースを修正してリビルドする

そもそもPlay frameworkのソースを修正してリビルドすれば自分の好みの動作をさせることができる。だがこの場合は環境構築に多大な手間を要することになるし、バージョンアップに追従しなければならない。このコストはあまりに大きいのでボツ。

2: パーセントエンコーディングのパーサーにかかる前にリクエストのヘッダを修正する

かつて僕はこのアプローチを採ろうとしたが失敗した。ヘッダを書き換えることでcharsetを指定しても、Playは正しくデコードしてくれなかった。僕の方法がまずかったかもしれないが、できないものはできないのでボツ。

3: 生の文字列としてリクエストを受け取り、手動でShift_JISとしてパースする

最終的に僕が辿り着いたのがこのアプローチだ。コントローラはひとまずリクエストボディを生の文字列として受け取り、オリジナルのパーサに処理させてリクエストを作りなおし、本来のリクエストとすげ変える。次項ではこれについて解説する。

Play framework 2.3.xでShift_JISのボディを処理する

まずはコードを見てほしい。

Play 2.3.x でShift_JISのパーセントエンコーディングされたデータを受け取る

ソースコードのポイントは、Actionの引数にparse.tolerantTextを指定することで、bodyを生のStringとしてパースしてもらう事だ。これによりPlayは自動的にUTF-8のフォームデータとしてbodyをパースしなくなるので、手動でbodyをパースすることができる。Actionの引数を省略すると、Action (parse.anyContent) {...}が指定されているのと同じになる。anyContentは、自動的にbodyの内容からパーサーを決めてくれる。この場合ならurlFormEncodedが呼び出されるだろう。

ちなみにplay.api.mvc.BodyParsers.parseには、ファイルやXMLなどのデータを自動的に処理してコントローラに送るための様々なパーサーが用意されているので見ておくと良い。

まとめ

Shift_JISは絶滅してほしい。これを解決するのにものすごい時間がかかったので後学のためにメモしておきます。お行儀の良いクライアントが増えますように。