例のごとくドキュメントが少ないので、Liftのソースを追ってみます。通常、テンプレートを使用したページは、XhtmlResponseとしてNodeが保持され、LiftServletの最後にtoResponseでInMemoryResponseに変換(*1)してから送られます。
// net/liftweb/http/LiftServlet.scala(l.183)
resp match {
case Full(cresp) =>
val resp = cresp.toResponse // *1 LiftResponse.toResponseはInMemoryResponseを返す
logIfDump(req, resp)
sendResponse(resp, response, Full(req))
//...後略...
問題1:文字コード
XhtmlResponseのtoResponseで何をやっているのか見てみると、mixinしているNodeResponse.toResponseで、utf-8とハードコードされています…。// net/liftweb/http/LiftResponse.scala(l.415)
InMemoryResponse(ret.getBytes("UTF-8"), headers, cookies, code)
問題2:Content-Type
次、Content-Typeにもcharset=utf-8が勝手に出てしまいます。どこでやっているかと調べると、LiftServlet.sendResponseでContent-Typeをセットしています。determineContentTypeはここでもまたutf-8がハードコードです。ただし、ここでは明示的にContent-Typeを指定しておけば、上書きはしないようです。// LiftServlet.scala(l.482)
// insure that certain header fields are set
val header = insureField(fixHeaders(resp.headers), List(("Content-Type", // *2
LiftRules.determineContentType(pairFromRequest(request))),
("Content-Length", len.toString)))
// LiftRules.scala(l.168)
@volatile var determineContentType: PartialFunction[(Box[Req], Box[String]), String] = {
case (_, Full(accept)) if this.useXhtmlMimeType && accept.toLowerCase.contains("application/xhtml+xml") =>
"application/xhtml+xml; charset=utf-8"
case _ => "text/html; charset=utf-8"
}
- insureFieldはnet.liftweb.util.HttpHelpersのメソッド。第1引数のList[Pair]に第2引数のList[Pair]の_1が含まれてない場合に、追加する。
問題3:XML宣言
さらに、<?xml version="1.0" encoding="UTF-8"?>のXML宣言も必ず付けてくれるようです。が、S.skipXmlHeaderをtrueにしておけば、何もしないようです。// net.liftweb.http.LiftRules.scala(l.442)
@volatile var calculateXmlHeader: (NodeResponse, Node, Box[String]) => String = {
case _ if S.skipXmlHeader => ""
case (_, up: Unparsed, _) => ""
case (_, _, Empty) | (_, _, Failure(_, _, _)) =>
"\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/html")) =>
"\n"
case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/xml") ||
s.toLowerCase.startsWith("text/xhtml") ||
s.toLowerCase.startsWith("application/xml") ||
s.toLowerCase.startsWith("application/xhtml+xml")) =>
"\n"
case _ => ""
}
対策
LiftRulesに、responseTransformersというものがあり、ここでLiftResponseが送られる前に書き換えることができます。最終的に、Boot.scalaで以下の様なことをしてやると、好みの文字コードでレスポンスを返すことができました。def boot {
//...省略...
LiftRules.responseTransformers.append(conv2sjis)
}
private def conv2sjis(org: LiftResponse): LiftResponse = {
org match {
case x: XhtmlResponse =>
S.skipXmlHeader = true // *1
val m = x.toResponse // *2
val h = x.headers ::: ("Content-Type", "text/html; charset=Shift_JIS") :: Nil // *3
InMemoryResponse(new String(m.data, "utf-8").getBytes("Shift_JIS"), h, m.cookies, m.code) // *4
case _ => org
}
}
- XML宣言を付けないようにS.skipXmlHeader=trueにセット。
- 一度、InMemoryResponseに変換
- Content-TypeヘッダをListに追加。
- SJISに変換し、新たなInMemoryResponseを返す。(LiftResponseはケースクラスなので書き換えられない)
0 件のコメント:
コメントを投稿