Mongrelで大きなファイルをダウンロードするとき。
Mongrelで数百メガもあるような巨大ファイルをダウンロードさせようとして、controllerでsend_fileを使うと、Rails内でNoMemoryErrorを起こして止まってしまう。
色々調べると「LightyでX-Sendfile使え」とか出てくる。でも実験とかデモの適当なアプリはMongrelだけで片付くとありがたい。
と思ってやり方を調べたのでメモ。
ファイルダウンロード用のMongrelハンドラを用意。
DirHandlerを使いたいんだけど、IEのへんな癖*1を回避するため、専用のハンドラを書く。
lib/download_hander.rb
require 'mongrel' module Mongrel class DownloadHandler < DirHandler def send_file(req_path, request, response, header_only=false) response.header["Content-Disposition"] = "attachment; filename=\"#{File.basename(req_path)}\"" super end end end
ユーザ認証とか必要ならここでやるのかな? 後述のMongrel設定かな?
まぁ適当アプリなので深く追求はせず。おなじく適当アプリということで、Railsにハンドラを認識させるため、config/environment.rbの末尾に
require "#{RAILS_ROOT}/lib/download_handler"
を追加する。
Mongrelの起動時スクリプトファイルを用意。
以下のような起動時スクリプトファイルを準備して、特定のURLをハンドラ経由でディレクトリに接続する。
config/mongrel.conf
require "#{RAILS_ROOT}/lib/download_handler" uri "/pictures", :handler => DownloadHandler.new("/www_data/pictures", false) uri "/texts", :handler => DownloadHandler.new("/www_data/texts", false)
アクセスする。
ブラウザで「http://localhost:3000/pictures/ファイル名」とすれば、/www_data/picturesにあるファイルをハンドラ経由で、Mongrelの send_file を使ってダウンロードできる。
この方法ならギガバイトなファイルでもOKなはず。
コントローラ経由でなら。
コントローラで、
redirect_to "/pictures/#{filename}"
とかやればOK。だと思う。
感想。
セキュリティを別にすれば、とりあえずはこれで処理はうごく。
え?そんなでっかいファイルをダウンロードしちゃいけませんか。でも業務アプリは泥臭さが命ですからー。
ちゃんとやるならLightyでね。
*1:Content-typeを無視する例のアレ。