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)

Mongrelを起動する。

mongrel_rails start -S config/mongrel.conf

こんな感じで起動する。

アクセスする。

ブラウザで「http://localhost:3000/pictures/ファイル名」とすれば、/www_data/picturesにあるファイルをハンドラ経由で、Mongrelの send_file を使ってダウンロードできる。
この方法ならギガバイトなファイルでもOKなはず。

コントローラ経由でなら。

コントローラで、

redirect_to "/pictures/#{filename}"

とかやればOK。だと思う。

感想。

セキュリティを別にすれば、とりあえずはこれで処理はうごく。
え?そんなでっかいファイルをダウンロードしちゃいけませんか。でも業務アプリは泥臭さが命ですからー。
ちゃんとやるならLightyでね。

*1:Content-typeを無視する例のアレ。