2011年8月12日金曜日

rails3とnginxでx-sendfile的な事をしてみる

■概要

rails3.0.9・nginxの組み合わせで、apacheでのX-Sendfile相当の事が実現したく検証しましたが、これまた少しはまりました。

■答えまでの道のり

config/environments/production.rb を見ると

# Specifies the header that your server uses for sending files
  config.action_dispatch.x_sendfile_header = "X-Sendfile"

  # For nginx:
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

という記載があるので、上をコメントアウトし、下をコメント外せば、OKと思ったのですが甘かったです。。上手く行かずエラーが出ます。

nginxのドキュメントを見ても、internalに対応する内部向けurlが、必要なのは分かりますが、いまいち良くわかりません。

ではrailsのsend_fileメソッド(actionpack-3.0.x/lib/action_controller/metal/streaming.rb)のソースを確認しても、pathには絶対パスを渡さないと上手く行かない事が予想されます(冒頭でファイルシステムとしての存在チェックをしているので)。しかしよく見ると

# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via

と書いてあるので、次はRack::Sendfile(rack-1.2.x/lib/rack/sendfile.rb)を見てみました。コメントには下記の用にnginx用のサンプルがあります。

  #   location ~ /files/(.*) {
  #     internal;
  #     alias /var/www/$1;
  #   }
  #
  #   location / {
  #     proxy_redirect     off;
  #
  #     proxy_set_header   Host                $host;
  #     proxy_set_header   X-Real-IP           $remote_addr;
  #     proxy_set_header   X-Forwarded-For     $proxy_add_x_forwarded_for;
  #
  #     proxy_set_header   X-Sendfile-Type     X-Accel-Redirect;  # ①
  #     proxy_set_header   X-Accel-Mapping     /files/=/var/www/; # ②
  #
  #     proxy_pass         http://127.0.0.1:8080/;
  #   }

アプリケーションの通常処理を行うlocation "/" と、ファイル転送用のlocation "~ /files(.*)"があり/var/wwwがマッピングされるのかな?と思い、真似してみましたが上手くいきません。。

もう少しRackのソースを読んでみると、上記設定の意味が良くわかります。①はvariationメソッドの戻り値になります。したがってcall内の分岐は"X-Accel-Redirect"に進みます。その後map_accel_pathメソッドを実行するのですが、ここで驚愕の事実に気付きます。

def map_accel_path(env, file)
    if mapping = env['HTTP_X_ACCEL_MAPPING']
      internal, external = mapping.split('=', 2).map{ |p| p.strip }
      file.sub(/^#{internal}/i, external)
    end
  end

②は絶対パスを、nginx用内部urlに変換する為に必要な設定ですが「右辺と左辺をドキュメントでは逆に書いている!」という事に気付きました。(internalとexternal)

よって②は左右逆転にしなければいけません。

proxy_set_header X-Accel-Mapping /var/www/=/files/;

ここまでやって上手くいきました。
/var/www/hoge.txt (send_file) => /files/hoge.txt (rackから出た時点) => /var/www/hoge.txt (nginxでの処理)

■設定方法(まとめ)

・config/environments/production.rb

config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

のコメントをはずし、一つ上のX-Sendfileはコメントアウトします。

・プログラム

ファイル送信部分は絶対パス指定します。

send_file "/var/www/baz.txt"

・/etc/nginx/nginx.conf

下記の様に、通常処理側には、X-Sendfile-Type・X-Accel-Mapping、内部処理向けには、internal・alias設定を記載すればOKです。

location / {
  ...
  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
  proxy_set_header X-Accel-Mapping /var/www/=/files/;
  ...
}

location /files/ {
  internal;
  alias /var/www;
}

0 件のコメント:

コメントを投稿