Dancerで、設定しているcharaset以外のコンテンツを返却するには
config.yml で設定しているcharsetは、UTF-8 である環境で、それ以外のcharsetのコンテンツを返却したい状況があった。具体的には、CSVファイルのダウンロードです。
ダウンロードしたファイルをExcelでそのまま開くと文字化けするので、CSVファイルのcharsetをcp932で返却してあげないといけない。 そもそも、いまだにcp932固定で開こうとするエクセルにびっくりしたんですが、仕方がないのでCSVファイルのコンテンツをcp932に変換することにした。
get '/csv/' => sub { # my $content = ....; content_type 'text/csv'; return Encode::encode('cp932', $content); }; |
しかし、ダウンロードして、エディタのencode指定でcp932を指定して開いてみると、文字化けしていた。 Perlのエンコード周りではまるのは恒例行事になってるので、Encodeの使い方を調べたりいろいろしてみたけど改善せず。
結局、Dancerがレスポンスを返却するときに、多重でencodeが実行されてしまっているのが原因だったようです。
Dancer のコード。Dancer/Handler.pm
unless ( ref($content) eq 'GLOB' ) { my $charset = setting('charset'); my $ctype = $response->header('Content-Type'); if ( $charset && $ctype && _is_text($ctype) ) { $content = Encode::encode( $charset, $content ) unless $response->_already_encoded; $response->header( 'Content-Type' => "$ctype; charset=$charset" ) if $ctype !~ /$charset/; } $response->header( 'Content-Length' => length($content) ) if !defined $response->header('Content-Length'); $content = [$content]; } else { if ( !defined $response->header('Content-Length') ) { my $stat = stat $content; $response->header( 'Content-Length' => $stat->size ); } } |
対策としては、responseに対して、$response->_already_encoded が、1 を返す状況にしてあげれば、 多重のencodeは防げそうだったので、afterのhookで、レスポンスがCSVの場合は、$response->{encoded}に 無理矢理 1を代入してしまうようにした。(_already_encoded メソッドは、$response->{encoded} を返す実装だったので)
hook after => sub { my $response = shift; if ($response->header('Content-Type') =~ m{text/csv}) { $response->{encoded} = 1; } }; |
これで、なんとかCSVのダウンロードの時だけ、encodeが重複して実行されるのを避けてcp932のコンテンツを返却できるようになった。 hookを使わずに、コンテンツがencode済であることを指定することってできるんでしょうか? よりスマートな方法があったら教えてください。