Rails1.2.3 x_sendfileを使った高速Streaming

Railsのsend_file

認証されたユーザにのみファイルのダウンロードを許可したい場合、公開ディレクトリにファイルを配置するわけには行かないので、アプリケーション側から送信制御を行うことになります。しかし、大容量のファイルを送信する場合、ファイル全体が一度にバッファに読み込まれてしまうと、メモリの使用率が大変なことになります。Railsのsend_fileは

action_controller/streaming.rb

# Sends the file by streaming it 4096 bytes at a time.

ということで、一度にバッファに読み込むサイズがデフォルトで4096バイトに制限されているのですが、

  1. アプリケーションを通すのでやはり遅い
  2. Apache + Mongrelだと4096バイトの制限が上手く効かない

という問題があります。2のソースはこちら

x_sendfile

上記の解決策として、x_sendfileを利用することができます。x_sendfileはlighttpdapacheで利用できるローカルファイル送信用モジュールで

  • アプリケーションで認証されたユーザのみにファイル送信を制御できる
  • Webサーバのみで送信処理を行うため高速

と言う特徴があります。
x_sendifleはlighttpdではデフォルトで利用できるようになっています。参考記事はこのあたり
apacheだとmod_xsendfileをロードしてconfに設定を書いてやる必要があります。Apache2.2での設定の詳細はこちらで。

Railsでx_sendfileを利用する

x_sendfileは

  1. ユーザがリクエストを投げる
  2. サーバアプリがユーザの認証を行う
  3. サーバアプリがx_sendfile用のヘッダの付いたresponseを返す
  4. ユーザとWebサーバの間で(サーバアプリを介さず)ファイルの送受信を行う

と言う手順で動きます。アプリケーション側では実際の送信処理を行わず、クライアントへ適切なレスポンスを返すだけでよいので簡単です。Railsの公式に以下のようなサンプルが乗っています。*1


filename = "/var/www/myfile.xyz"
response.headers['Content-Type'] = "application/force-download"
response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filename)}\""
response.headers["X-Sendfile"] = filename
response.headers['Content-length'] = File.size(filename)
render :nothing => true
これはApacheの例ですが、lightyの場合は"X-Sendfile"でなく"X-LIGHTTPD-SEND-FILE"と指定します。

プラグインを利用

上記を自分で書いてやってもさほどの手間ではありませんが、プラグインをつかうとさらに便利です。製作者のサイトを参考にプラグインをインストールしてみます。


ruby script/plugin install http://john.guen.in/svn/plugins/x_send_file/
利用はほとんどsend_fileと同じ

x_send_file('/path/to/file')
x_send_file('/path/to/image.jpg', :type => 'image/jpeg', :disposition => 'inline')
ただし、デフォルトの設定はApache向けになっているので、lightyで利用する場合は以下のような感じで

x_send_file('/path/to/file/', :header => 'X-LIGHTTPD-SEND-FILE')
さらに、environment.rbに以下の設定を追加すると、send_fileの中の動きが全てx_send_fileに置き換わってくれます。

XSendFile::Plugin.replace_send_file!
可搬性を考えるとこの方法がベストかもしれません。

*1:ページ中のXSendFileAllowAboveについては、Apache2.2向けのモジュールからはセキュリティ強化のためXSendFilePathというものに変わっています。"XSendFilePath /var/secure_files"のように、利用を許可するディレクトリを設定できるようになっています。こちらを参照