今日もPassenger2010/03/11

完全に自分用メモです。

Shibboleth SPを Passengerで配置したアプリと協働させるようにするには

RAILS_ROOT/public/Shibboleth.sso/SAML/POST
RAILS_ROOT/public/Shibboleth.sso/SAML2/POST

に内容は何でも良いので、何かファイルを置く。 参考:Shib-Users-ML

ファイルが実在すれば、mod_passenger は仕事をしないので、mod_shibが割り込む余地ができるというところで、 mod_rewrite の時に

ProxyPass        /Shibboleth.sso !

などと書いていたのと概念的には同じことだろう。

Passenger とか RVM とか2010/03/10

色々サボっていた分のキャッチアップをしたので忘備録

インストール順に RVMから

RVM

RVM は、複数バージョンのruby処理系を同時にインストールして、 それらをスイッチしながら利用することを可能にしてくれるライブラリ。

gemで配布されているけど、実体は殆どシェルスクリプト。

ちょっと考えれば何をやっているのかはすぐに判ると思う。でも、これらのスクリプトが既製品で用意されたというのは大きい。

複数のバージョンの ruby に順番に同じコマンドを流して、結果を比較できる機能は結構楽しい。

$ rvm ruby -v
jruby-1.4.0: jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3) (OpenJDK Client VM 1.6.0) [i386-java]
jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3) (OpenJDK Client VM 1.6.0) [i386-java]

ruby-1.8.6-p399: ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-linux]
ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-linux]

ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-linux]
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-linux]

ruby-1.9.1-p378: ruby 1.9.1p378 (2010-01-10 revision 26273) [i686-linux]
ruby 1.9.1p378 (2010-01-10 revision 26273) [i686-linux]

あと、0.18 ぐらいでいくつかのオプションの記法が変更されている。 例えば、インストールされたruby処理系の一覧の取得には 'rubies' の引数が必要になった。

$ rvm list rubies
rvm Rubies
   jruby-1.4.0 [ [i386-java] ]
   ruby-1.8.6-p399 [ i386 ]
=> ruby-1.8.7-p249 [ i386 ]
   ruby-1.9.1-p378 [ i386 ]

Default Ruby (for new shells)
   ruby-1.8.7-p249 [ i386 ]

System Ruby
  system [ i386 ]

Passenger

Phusion Passnger は通称mod_rails(ドメイン名もそうだ)。 Apacheの拡張モジュールとして動作し、rubyのプロセスをキャッシュしてくれる素敵なやつ。

噂には聞いていたけど、実際動作させてみるとお手軽さが凄い。 確かに mogrelのプロセスを自前でspinさせていた時代には戻れない気がする。

RVM経由でビルドした場合(かつ RVMサイトのインテグレーションガイドに従った場合) 共有モジュールの.soが、

/home/user/.rvm/gems/ruby-1.8.7-p249/gems/passenger-2.2.10/ext/apache2/mod_passenger.so

とか言う結構素敵な場所にできます。 素のrubyからインストールしても、/lib/ruby/gems/1.8/gems/~ とかApacheのモジュールとしては結構ナニな場所だけどね。

このせいで、うちの環境だと、SELinux のチェックにひっかかってしまった。

Apache の起動時に mod_passenger.so を読み込もうとしたところで Permision Denied のエラーで落ちる。

この場合、SELinux を Enfoced か Disabled に設定すること。

Capistorano

deploy時にシンボリックリンクを張り代えるので、Passenger との相性が気になったけど、 今のところは問題なさそう。

Passenger は シンボリックリンクが張り代わった直後のアクセスで($RAILS_ROOT/tmp/restart.txt が無くても) 再読み込みされているっぽい。

念のため Capfile で deploy:restart タスクに restart.txt に touch するようにしておく。 (もはや、script/process/reaper なんて存在しないしね)

namespace :deploy do
  desc "Restarting mod_rails with restart.txt"
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "touch #{current_path}/tmp/restart.txt"
  end

  [:start, :stop].each do |t|
    desc "#{t} task is a no-op with mod_rails"
    task t, :roles => :app do ; end
  end
end
http://tomcopeland.blogs.com/juniordeveloper/2008/05/mod_rails-and-c.html

Ruby のHTTP関連ライブラリ と ストリーミングダウンロード2010/03/01

うーむ。 表題の件で少し悩み中

Net::HTTP / Net::HTTPS

Net::HTTP#request は、ブロックを渡すとストリーミング受信はできるものの、レスポンスは全部メモリに貯まる。

微妙な片手落ち感が……。

Mechanize

Mechanize#get 等はブロックを受け取るものの、ストリーミング受信等はしてくれない。

Mechanize::Page オブジェクトの生成が全て完了してからブロックがyeildされる。 というか、このブロック引数って、何か良いことあるのかな? eachが一つ省略できるぐらい?

HTTPClient

HTTPClient#request は、ブロックを渡すとストリーミング受信をしてくれて、この場合は、responseオブジェクトにはbodyが貯まらない。

この点については一番理想的な実装だけど、信頼できない証明書を使用しているhttpsサイトに接続させてくれないという問題が……。

HTTPClient にモンキーパッチを当てるのが正着なのかなあ……。

と思ったら、 HTTPClient::SSLConfig#verify_mode に OpenSSL::SSL::VERIFY_NONE を設定してやれば証明書の問題はスルーしてくれることが判明。

httpclient = HTTPClient.new
httpclient.get('https://localhost/').status
# OpenSSL::SSL::SSLError が発生
httpclient.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
# => 0
httpclient.get('https://localhost/').status
# => 200

調べてみたら、HTTPClient 2.1.3 からの新機能か。1年前には実装されてたとは……。時の流れは速すぎるぜ。

余談

ちなみに、request bodyをストリーミングできるのは Net::HTTPだけ。

余談の余談

ここで言うストリーミングとは、rewindできないようなIO(要するに$stdin)の状態で取り扱いたいという話で、既にFileオブジェクトになっているようなものを少しずつ送るだけなら、MechanizeやHTTPClientでも問題なく対処できます。

Rails Metal と Controller のセッション共有2010/02/26

Railsノート - セッションまわりを読む (1)

ここが詳しい。

Controller クラスのインスタンスメソッド session で参照できる Hash と同じものが、 Metal クラスでは env['rack.session'] で参照できる。

(env は call の引数)

Rails Metal の URIマッピング2010/02/26

発端

passenger + Rack 環境にアプリを置こうと思ったのだけど、 いろいろあって、「ActiveRecord があった方が良くね?」という意見が発生。

「それだったら、Rails Metal で良くね?」という結論が、脳内会議で決議された訳だけど、そこで悩んだのが、URIマッピングのルールが良く判らない、という点。

ソースはあんまり見てない(特にroutes関係)なので、あまり信用しない方が良いけど、何かの役に立つかも知れないので、晒しておく。

テンプレート

$ ./script/generate metal foobar

の結果作成される app/metal/foobar.rb のテンプレートは

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Foobar
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/foobar/
      [200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

こんな感じになる。 これは(require行とその作用を除けば)、非常に単純なRackアプリケーションそのものなので、 詳細は省略する。

これで、

$ ./script/server &

$ curl http://localhost:3000/foobar
Hello, World!

foobar 以下のURIがこの metal で処理されていることが判る。

routes.rb ?

Rails アプリケーションのURI情報の設定なら、config/routes.rb が正道だ、と思ったが、 どうも routes.rb には metal 関係のキーワードは定義されていない。

ルーティング情報はどこに?

となると、怪しいのは

    if env["PATH_INFO"] =~ /^\/foobar/

この if文だという訳で、regexpを書き換えたりしてみたところ、どうもこいつがルーティング情報らしいことが判明。

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Foobar
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/hogehoge/
      [200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

だと

$ curl http://localhost:3000/foobar
(略)
Routing Error
(略)
$ curl http://localhost:3000/hogehoge
Hello, World!

になる。

まとめ

  • metal は routes.rb に書いた規則よりも前に実行される。
  • metal は全てのURIに対して実行される。
  • metal が、404のレスポンスを返した場合は、他の metal(あれば)、routes.rb の規則の適用の順にリクエストが取り回される。
  • 複数の metal が存在する場合の実行順は未定義(ファイルのiノード順っぽい)

ということみたいだ。

演繹できる事実として、同名の metal と controller が居た場合は metal が勝つのと、 controller に落ちるまでには metal が全て実行されるので、極端に metal のクラスが多い場合は要注意かも知れない。

あと、passenger と共に使用する場合は、metal の中で、request.body() とかやってしまった後に、404で次の metal や controller に処理を 回すと大変な事になるような気がする。

(passenger の Rackインターフェイスに入ってくる request.body()はrewindableなオブジェクトではないため)

追記

長々と書いた後で、config/config.ru に rackup用のconfigが書けることに気がついた。

ここなら、 マッピングルールを自在に書ける。