Ruby on RailsをIIS7 FastCGIでホストする

丸二日かかったが、なんとかRailsアプリケーションをWindows VistaIISからホストできたので、まとめてみた。

内容に関しては全面的に以下のサイトを参考にさせて頂いた。これらの情報が無かったら今回の成功は絶対に無かっただろう。
HowToConfigureIIS7 in Ruby on Rails - Ruby on Rails Wiki
Ruby on Rails in IIS 7.0 with URL Rewriter - RuslanY Blog
内容は似ているものの違いがあるが、基本的には日付が新しいRuslanY Blogの解説を参考にさせて頂き、あとは自分の環境で上手くいった内容を使っている。必要無かった作業に関しては割愛しているが、あくまで私の環境であって、他の環境ではまた違うかもしれない。(RubyRailsもマイナーバージョンが違うだけで結構違いがあるので、一つずつ動作を確認していくしかない)

  • 使用したソフトウェアとバージョン

Windows Vista Bussiness(Ultimate) + Service Pack 1
Ruby 1.8.7 [i386-mswin32]
Ruby On Rails 2.2.2
RubyForIIS
Microsoft URL Rewrite Module for IIS 7.0, Go Live

  • 必要なソフトウェアの導入

1. IIS7をFastCGI機能込みでインストール
先日のエントリにもあるが、RubyIISからホストするにはFastCGIサポートが必要だ。FastCGIサポートはWindows Vista SP1のIISのCGI機能に含まれているので、単にCGI機能付きでIIS7をインストールすれば良い。

2. RubyForIISをインストール
RubyForIISはFastCGIのサポートをRubyに追加するライブラリィ。インストーラが付属しておりRubyディレクトリ内に必要なファイルをコピーしてくれはずだが、必ずしも必要なスクリプト(fcgi.rb)がパスが通った場所に配置されるとは限らないらしいので同スクリプトがロードできるか、IRB等で確認しておく必要がある。

例) fcgiがロードできるか確認する

irb(main):002:0> require 'fcgi'
=> true
irb(main):003:0>

もしこれでロードが失敗する場合、RubyForIIS下のファイル全てを$RUBY_HOME下等にコピーして、再度チェックする。

実は、当初作業したときにこの確認を怠ったために、後でエラーとなってはまった。
" scriptProcessor could not be found in application configuration"

このエラーだけ見ても原因はまず分からないので、ゆめゆめ注意が必要だ。

3. Microsoft URL Rewrite Module for IIS 7.0をインストール
MicrosoftがIIS7のURL書き替えの為のモジュール。IIS7のマネジャのスナップインとしてURL書き替えのルールを編集できる。
後述するが、Railsは自らにルーティングされる内部パスしか処理することができないため、アプリケーション外の静的なURLは処理できない。また、Railsは一部のリソースに対してキャッシュされるのを防止するために特殊な'アセット'サフィクスが付加されるが、アセットが追加されたURLはRailsのハンドラは適応できてもIISのファイルのハンドラは対応できずに正しいリソースを探し出せない。URL Rewrite Moduleはこれらの問題をFIXするために使用される。

  • IISへのリクエストをFastCGIのハンドラにマッピングする

必要なソフトウェアの導入が完了したならば、IISのマネジャを使用してサイトを作り、アプリケーションを対象のRailsアプリケーションにマッピングする。
IISのサイトとRailsのアプリケーションのマッピング方法はいろいろなやり方があると思うが、簡単なのはIISの管理上の'サイト'をRailsのアプリケーションのルートに直接マップすることだ。これだとWEBRickでテストする場合とURLは変わらないが、IISは同時に開けるサイトは一つだけなので、複数のアプリケーションを同時に使用する場合はポートを変える必要が出てくる。

もう一つはサイトの配下に複数のRailsアプリケーションを作成する方法だ。

e:
cd www
rails application1
:
rails application2
:
:

この方法だと幾つアプリケーションを増やそうが、サイトは一つなので管理は楽になる。
(この方法だとIISがマップするパスとRailsアプリケーションがルーティングするパスが一致しなくなる。とりあえずベタにroutes.rbを変更して、アクセスできるようにしたのだが、結局これが後に問題とになる。)

例) application1のroutes.rb

ActionController::Routing::Routes.draw do |map|
  map.connect 'application1/:controller/:action/:id'
  map.connect 'application1/:controller/:action/:id.:format'
end

サイトとアプリケーションのマップが出来たら、FastCGIからrubyを起動するためにハンドラマッピングを追加する。

IISマネジャ->サイトを選択->ハンドラマッピング->モジュールマップの追加

このようにGUIで設定しても良いが、web.configのフラグメントを作成して対象アプリケーションのルートに配置することでハンドラマッピングをアプリケーション毎に設定できる。(というかこの方が簡単)
web.configの例 (addressbook)


    
    
        
        
        
    
  

この例では、$RUBY_HOMEをe:\ruby、IISのサイトをe:\www、railsで作成したサンプルのアプリケーションとしてe:\www\addressbookとしている。(routes.rbも修正済み)
アプリケーションはとりあえず動作確認だけできれば良いので、以下のコマンドで必要最小限のアクションだけを追加した。

e:\www\addressbook>ruby ./script/generate controller test index about

test_controller.rb

class TestController < ApplicationController
  def index
    render :text=>"The index action"
  end
  def about
    render :text=>"The about action"
  end
end

以降、Webブラウザから以下のようにURLを指定することで上記のアクションが実行することを確認できれば成功だ。

http://locahost/addressbook/test/index
http://locahost/addressbook/test/about

ちなみにここは今でも解らないのだが、scriptProcessorc属性で設定する値としてはruby.exeへのパスとパイプで渡すスクリプトへのパスを書く、ここまでは良いのだが、その後のパラメタ"development"は必要なのか? ということ。

scriptProcessor="E:\ruby\bin\ruby.exe|E:\www\addressbook\public\dispatch.fcgi development"

wiki.rubyonrails.orgの記事では書くようには説明が無いが、ruslany.netの記事ではこのパラメタの記述があるのだ。パラメタの名前からして、環境変数 'ENV_RAILS'の規定値だと思うのだが、ここで説明通りパラメタを記述しておくと、以下のようにエラーになってしまうのだ。

なお、このようにweb.configを作る場合、ルートの設定(applicationHost.config)の特定のセクションを上書き(override)することとなるが、IIS7のデフォルトの設定では各セクションの上書きは禁止("Deny")になっているので、そのままでは設定がエラーになってしまう。
従って、applicationHost.configの該当セクション、つまり"handlers"セクションの上書きを許可するように修正する。


    
: :

さて、実際にアクセスしてみよう。以下のようにテキストだけがレンダリングされれば成功だ

  • 他のモードでの実行

Railsは「開発モード」「テストモード」「製品モード」を環境変数で切り換えることができるが、IISを使う場合、上記のapplicationHost.configにおけるfastCgi要素に環境変数'RAILS_ENV'を記述することでモードを設定できる。








なお、これは既に紹介したが、"Administration Pack"を使うことでGUIで編集することも可能。

  • Microsoft URL Rewrite Moduleを使ってRailsだけではサポートできない静的なURLに対応する

Ruby(Rails)をリクエストハンドラにした場合、アプリケーションが動作する動的なURLは勿論のこと、イメージやスクリプトなどの静的なリソースも全て処理対象になる。この場合、アプリケーション以外のパス上にあるリソースはRailsのルーティングの対象外になるため、Railsのリクエストハンドラではルーティングを解決できずにエラーとなってしまうことがある。
このような問題はURLの書き換えを使って回避することができる。IIS7にも幸いURL書き換えを機能に追加することができるため、これを使って静的なリソースに関しては、IISの静的なファイルを処理するハンドラにリクエストを委譲してやれば良い。
具体的には、以下のURL書き換えルールをIIS URL Rewrite Moduleに対してインポートしてやるだけだ。

# Redirect all requests not available on the filesystem to Rails   
RewriteEngine On   
RewriteRule ^$ index.html [QSA]   
RewriteRule ^([^.]+)$ $1.html [QSA]   
RewriteCond %{REQUEST_FILENAME} !-f   
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]  

IISのURL RewriteModuleはmod_rewriteルールのインポートが可能であり、上記のルールをそのまま貼りつけることができる。素晴らしい。

なお、上記のルールで書き換えたURLとそれに伴うリクエストはRailsで処理するためにdispatch.fcgiというスクリプトで実行されなければならない。それが最後のルールにもなっているのだが、このように修正した場合、アプリケーションにマップしたハンドラのパスも"*"ではなく、dispatch.fcgiにする必要がある。


インポートしたルールの内容は適用するとweb.configに反映される。以下、書換ルールが反映された、addressbookアプリケーションのための最終的なweb.configの内容だ。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
    <handlers>
        <clear />
        <add name="Addressbook" path="dispatch.fcgi" verb="*" modules="FastCgiModule" scriptProcessor="E:\ruby\bin\ruby.exe|E:\www\addressbook\public\dispatch.fcgi" resourceType="Unspecified" />
        <add name="Static" path="*" verb="*" modules="StaticFileModule" resourceType="File" />
    </handlers>
        <rewrite>
            <rules>
                <rule name="Imported Rule 1" enabled="true">
                    <match url="^$" ignoreCase="false" />
                    <action type="Rewrite" url="index.html" appendQueryString="true" />
                </rule>
                <rule name="Imported Rule 2" enabled="true">
                    <match url="^([^.]+)$" ignoreCase="false" />
                    <action type="Rewrite" url="{R:1}.html" appendQueryString="true" />
                </rule>
                <rule name="Imported Rule 3" enabled="true" stopProcessing="true">
                    <match url="^(.*)$" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" pattern="" ignoreCase="false" />
                    </conditions>
                    <action type="Rewrite" url="dispatch.fcgi" appendQueryString="true" />
                </rule>
            </rules>
        </rewrite>
  </system.webServer>
</configuration>

IIS7-FastCGIから実行したRuby on Railsは当然だがWEBRickを使った場合よりも、一度目のロードに時間がかかるが、きびきびと動く印象である。
ただ、このような変則的?な実行環境の場合、なにかしら制限があるので、暫く慎重に見ていく必要があるだろう。