2013年9月3日火曜日

Dispatch(reboot)で文字化けが起きた時の対応

今日これで30分ぐらい無駄にしたので忘れる前にメモ。

概要


Dispatchを使ってHTTPクライアントを作ると、稀に文字化けすることがあるので、その原因と対策をまとめる。
ちなみにDispatchはAsyncHttpClientを内部で使っていて、今回の話題はAsyncHttpClientの領域にも少し踏み込む。

原因と解決方針


文字化けの原因はHTTPクライアントにはよくある話で、HTTPレスポンスヘッダのContent-Typeにcharsetがセットされていない(または間違っている)のが原因。
Bodyバイト列をStringに変換するときにContent-Typeのcharsetを用いるのだが、デフォルトではISO-8859-1が使われるため、charset未設定のヘッダが来ると、bodyの文字コードが例えUTF8であっても化けてしまう。

で、これの解決方法として、ResponseFilterを使う。
ResponseFilterはAsyncHttpClient側の機能で、bodyの解析前に処理を挟むことができる。これを用いて、ヘッダのContent-Typeにcharsetを書き込んでしまえば良い。

DispatchからResponseFilterを挟む


あとは実際にコードを書くだけだが、今回はDispatchからの利用なのでDispatchのAPIからResponseFilterを挟む必要がある。実際にコードを見たほうが早いだろう。

import dispatch._
import com.ning.http.client.filter.{ ResponseFilter, FilterContext }
import scala.collection.JavaConverters._
val MyHttp = Http.configure { builder =>
builder.addResponseFilter(new ResponseFilter {
override def filter(ctx: FilterContext[_]) = {
ctx.getResponseHeaders.getHeaders.get("Content-Type").asScala.toList match {
case "text/html" :: Nil =>
ctx.getResponseHeaders.getHeaders.put("Content-Type", List("text/html; charset=utf-8").asJava)
case _ => ()
}
ctx
}
})
}
MyHttp(url("http://example.com") OK as.String)
view raw myhttp.scala hosted with ❤ by GitHub


Dispatchで提供されるデフォルトのHttpはconfigureメソッドを持っており、これにAsyncHttpClientConfig.Builderをいじる関数を渡してやることで、HTTPクライアントの挙動を制御することができる。今回はaddResponseFilterを使ってResponseFilterを挟んでやろうと言うわけだ。ResponseFilterの実装はcharset無しのContent-Typeが渡ってきたらcharset付きで上書きしてやろうというもの。Scalaの感覚で「Filter」というとFilterContextを受け取って新しいFilterContextを生成する実装をしたくなるが、ここでは単純にmutableなオブジェクトの操作でOK。

この例では"text/html"の時だけUTF-8を指定しているだけだが、必要に応じて実装を書き換えてやれば良い。

参考


Http.configureについてはこちら、AsyncHttpClientConfig.Builderについてはこちら、ResponseFilterについてはこちらを参照。

0 件のコメント:

コメントを投稿