15
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails日本語ファイル名がSafariやIEで文字化けする対策

Last updated at Posted at 2017-02-17

#Safari、IEでファイルをダウンロードする際の文字化対策
既存moduleの動作を上書きしてしまうことで解決できる。

ApplicationController.rb
class ApplicationController < ActionController::Base
  def send_file_headers!(options)
    super(options)
    match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
    encoded = URI.encode_www_form_component(match[2])
    headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
  end
end

ApplicationControllerにこのメソッドを定義すれば終わり!

以下は解説になります

Railsで日本語のファイル名をDLしようとするとSafariやIEで文字化けする

この様な状況に陥ってないでしょうか。

  • send_fileメソッドを用いてファイルを送ると日本語が文字化けする
  • ローカル環境ではうまくいくけれど、AWSサーバなどの本番環境だけで文字化けする
  • Chromeでは日本語で正常にDLできる
  • SafariとIEでDLすると文字化けする
  • サーバの文字コードやファイル名などをチェックしても問題なさそう

send_fileを用いてファイルをダウンロードさせようとすると、SafariやChromeにて文字化けすることがある。

download_controller.rb
def dowmload_file file_name, type:
  file_path = Rails.root.join('public', 'file', "解説書.pdf")
  send_file file_path, filename: "解説書.pdf", type: type
end

原因:Railsで実装されているDataStreaming Moduleが対応していない

send_fileメソッドを実行した際に呼び出される、ActionControllerのDataStreamingモジュールがSafariとIEの際に、
文字コードをエンコードせずに送信するためこのバグが起きている様です。

公式リファレンス :ActionController->DataStreaming
http://api.rubyonrails.org/classes/ActionController/DataStreaming.html

非推奨:alias_method_chainでのmodule書き換え

alias_method_chainを用いて既存のDataStreaming Moduleの処理を書き換えます。

このmoduleをシステムに読み込ませてDLするとあら不思議、日本語でDLできます。

data_streaming_module.rb
module ActionController
  module DataStreaming
    def send_file_headers_with_utf8!(options)
      send_file_headers_without_utf8!(options)
      match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
      encoded = URI.encode_www_form_component(match[2])
      headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
    end
    alias_method_chain :send_file_headers!, :utf8
  end
end

この様なモンキーパッチを一時的にシステムに組み込むことでRailsのバージョンが上がったて修正された際には
この部分のみ取り除くことでRails自体のソースコードに手を出すという闇から逃げられます。

#alias_method_chainはRails5から非推奨なのでprependで書き換えた
module自体を書き換えることで、次回以降にprependやincludeした際にも変更が有効になります
=>つまり複数箇所でmoduleをincludeやprependするならこの方式で書きましょう。

application_controller.rb
class ApplicationController < ActionController::Base
  #ActionController::Baseのソースを読むとここで様々なmoduleをincludeしている
  module DataStreamPatch
    def send_file_headers!(options)
      super(options)
      match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
      encoded = URI.encode_www_form_component(match[2])
      headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
    end
  end

  module ActionController
    module DataStreaming
      prepend DataStreamPatch
    end
  end

  #moduleの動作を書き換えた上で再度includeする必要がある。
  include ActionController::DataStreaming

  ....
end

#複数箇所でmoduleを利用しない場合はover writeで実装したほうが単純明快
もし複数箇所でこのmoduleを別々にprependしたりという用途で使わない場合はApplicationControllerにこのように定義してしまうのが
単純明快で良い!

ApplicationController.rb
class ApplicationController < ActionController::Base
  def send_file_headers!(options)
    super(options)
    match = /(.+); filename="(.+)"/.match(headers['Content-Disposition'])
    encoded = URI.encode_www_form_component(match[2])
    headers['Content-Disposition'] = "#{match[1]}; filename*=UTF-8''#{encoded}" unless encoded == match[2]
  end
end

紆余曲折した結果今提供しているサービスではこの実装で動いています。

#より詳細な解説はこの記事を参照ください
なぜこのような実装になるのか、なぜこれで解決するのかはこの記事に全てまとめました。
是非こちらを参照ください。

15
12
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?