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済であることを指定することってできるんでしょうか? よりスマートな方法があったら教えてください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください