active_adminのassets precompileが失敗する

active_adminのprecompileが失敗する。

環境

プリコンパイルしてみると..

$ bundle exec rake assets:precompile
/Users/hakutoitoi/.rbenv/versions/1.9.3-p194/bin/ruby /Users/hakutoitoi/.rbenv/versions/1.9.3-p194/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
rake aborted!
Undefined mixin 'global-reset'.
  (in /Users/hakutoitoi/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/activeadmin-0.5.1/app/assets/stylesheets/active_admin/_base.css.scss)

Tasks: TOP => assets:precompile:all(See full trace by running task with --trace)
rake aborted!
Command failed with status (1): [/Users/hakutoitoi/.rbenv/versions/1.9.3-p1...]

Tasks: TOP => assets:precompile
(See full trace by running task with --trace)

sassのmixinが未定義と言われて失敗する。
github issueでは色々議論されていて色んな解決法が書いてあるが、自分の環境ではconfig.assets.precompileの指定を変えたら成功するようになった。
https://github.com/gregbell/active_admin/issues/810

もともとはprecompileするファイルの指定はproduction.rbで下記のように指定していた。

  config.assets.precompile += ['*.js', '*.css']

アスタリスクでの指定をやめて正規表現で指定したら成功した。

  config.assets.precompile += [/^[a-z0-9]\w+.(css|js)$/]

protect_from_forgeryが何をやっているのか調べた

Rails3でCSRF対策としてApplicationControllerにデフォルト指定されるprotect_from_forgeryですが、実際のところ何をやっているのかわからなかったのでコードリーディングしてみたメモ。

環境

処理の流れ

0. ApplicationControllerに下記の指定がされているところから始まる

class ApplicationController < ActionController::Base
  protect_from_forgery
end

1. まずprepend_before_filterでverify_authenticity_tokenをbefore_filter郡に突っ込む

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 67
def protect_from_forgery(options = {})
  self.request_forgery_protection_token ||= :authenticity_token
  prepend_before_filter :verify_authenticity_token, options
end

2. verify_authenticity_tokenではverified_request?を呼ぶ

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 75
def verify_authenticity_token
  unless verified_request?
    logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
    handle_unverified_request
  end
end

3. verified_request?でリクエストデータを検証する

  • protect_against_forgery?は設定ファイルでallow_forgery_protection = falseが指定されていないか検証
  • GETリクエストか検証
  • sessionに含まれるtokenとリクエストパラメータにrequest_forgery_protection_tokenが指定されていてマッチするか検証
  • sessionに含まれるtokenとX-CSRF-Tokenヘッダのトークンが指定されていてマッチするか検証

の4項目を検証している。

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 93
def verified_request?
  !protect_against_forgery? || request.get? ||
    form_authenticity_token == params[request_forgery_protection_token] ||
    form_authenticity_token == request.headers['X-CSRF-Token']
end

4. 処理の分岐
verified_request?の検証で成功(どれか1つでもtrue)すれば何もしないでprotect_from_forgeryの処理は完了、
失敗(すべてfalse)だった場合はhandle_unverified_requestが呼ばれてさらにreset_sessionが呼ばれて
最終的にリクエストからsession情報がリセットされる。

# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 84
def handle_unverified_request
  reset_session
end
# File actionpack/lib/action_controller/metal/rack_delegation.rb, line 22
def reset_session
  @_request.reset_session
end
# File actionpack/lib/action_dispatch/http/request.rb, line 209
def reset_session
  session.destroy if session && session.respond_to?(:destroy)
  self.session = {}
  @env['action_dispatch.request.flash_hash'] = nil
end

まとめ

protect_from_forgeryはリクエストを検証して正しければ何もしない、正しくない場合はsessionをクリアする。

capistrano-unicornでハマった

capistrano-unicornはその名の通りcapistranounicornを扱う便利なgemです。
早速Railsのサービスに導入してみた。

試した環境は

githubの使い方を参考にGemfileに下記を追加してbundle叩く

group :development do
  gem 'capistrano-unicorn', :require => false
end

config/deploy.rbでrequireするのとdeploy:restart時のcollbackを登録

# set :application宣言より下に書かないとエラーになる
require 'capistrano-unicorn'

...

# hot deployを利用するため unicorn:restartを指定
after 'deploy:restart', 'unicorn:restart' 

unicorn設定ファイル(config/unicorn.rb)を作成

# Set your full path to application.
app_path = "/var/www/vhosts/blog/current"

# Set unicorn options
worker_processes 3
preload_app true
timeout 30
listen "/tmp/unicorn.blog.sock"

# Spawn unicorn master worker for user apps (group: apps)
user 'rubys', 'rubys'

# Fill path to your app
working_directory app_path

# Should be 'production' by default, otherwise use other env 
rails_env = ENV['RAILS_ENV'] || 'production'

# Log everything to one file
stderr_path "log/unicorn.log"
stdout_path "log/unicorn.log"

# Set master PID location
pid "#{app_path}/tmp/pids/unicorn.pid"

before_fork do |server, worker|
  ActiveRecord::Base.connection.disconnect!

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  ActiveRecord::Base.establish_connection
end

ここまでで準備はOKのようです、追加、変更したファイルはこんな感じで配置されます。

Gemfile
config
|-- deploy.rb
|-- unicorn.rb

準備も整ったのでcap deployしてみると..

  * 2012-12-22 02:19:17 executing `deploy:restart'
    triggering after callbacks for `deploy:restart'
  * 2012-12-22 02:19:17 executing `unicorn:reload'
  * executing "if [ -e /var/www/vhosts/realplay/current/tmp/pids/unicorn.pid ]; then echo 'true'; fi"
    servers: ["192.168.6.29"]
    [192.168.6.29] executing command
    command finished in 3739ms
*** [Unicorn] Stopping...
  * executing "kill -s USR2 `cat /var/www/vhosts/realplay/current/tmp/pids/unicorn.pid`"
    servers: ["192.168.6.29"]
    [192.168.6.29] executing command
    command finished in 3646ms
the task `unicorn:restart' does not exist

あれ、after 'deploy:restart', 'unicorn:restart'を呼んでるのにunicorn:reloadが呼ばれてる。
しかもunicorn:restartがないとも言ってるし。

github上のソースコード見る限りちゃんとrestart定義されてるし..

task :restart, :roles => :app, :except => {:no_release => true} do
  run <<-END
    if #{unicorn_is_running?}; then
      echo "Restarting Unicorn...";
      #{unicorn_send_signal('USR2')};
    else
      #{start_unicorn}
    fi;

    sleep 2; # in order to wait for the (old) pidfile to show up

    if #{old_unicorn_is_running?}; then
      #{unicorn_send_signal('QUIT', get_old_unicorn_pid)};
    fi;
  END
end

githubのmasterのversion.rbも0.1.6になってるし古いバージョンを入れているわけではなさそう。
しかたなくインストールされているソースを見るとなんとunicorn:restartが定義されていない。
どうもrubygems.orgに上がっているソースは最新ではないようなのでgithubからインストールするように変更する。

group :development do
  gem 'capistrano-unicorn', :git => 'git@github.com:sosedoff/capistrano-unicorn.git'
end

再度deployしてみる。

  * 2012-12-22 04:25:11 executing `deploy:restart'    triggering after callbacks for `deploy:restart'
  * 2012-12-22 04:25:11 executing `unicorn:restart'
    triggering before callbacks for `unicorn:restart'
...
    [192.168.6.29] executing command
 ** [out :: 192.168.6.29] Restarting Unicorn...

想定どおり、unicorn:restartが動いてくれた。