Assumptions For Working Configuration (as tested)
 - Windows XP or Server 2003 running IIS 5.1 or IIS 6.0
 - Ruby Version 185-21
 - Rails-1.0.0
 - Rake-0.6.2
 - ActiveRecord-1.13.2
 - ActiveSupport-1.2.5
 - ActionWebService-1.0.0
 - ActionPack-1.11.2
 - ActionMailer-1.1.5
		

Ruby on Rails - Fast-CGI for IIS - Manual Configuration


  1. Open the IIS (Internet Information Services) MMC snapin console.
  2. Create a new Web Site (Steps 1-7) or use Default Web Site (Steps 8-12)
    1. In the MMC snapin, expand the tree until you see "Web Sites" folder.
    2. Right Click -> New -> Web Site...
    3. Click Next and provide a description, click Next.
    4. Setup any IP and port settings, click Next.
    5. Browse to your Ruby on Rails application root's public folder. This is the same location you specified during the installation process. Click Next. ex. The path ( C:\ruby\applications\rails_root\public or #{RAILS_ROOT}).
    6. You need to allow Read, Run scripts and Execute permissions, click Next.
    7. Click Finish.
    8. In the MMC snapin, expand the tree until you see "Web Sites" folder.
    9. Right Click Default Web Site -> Properties

    10. Set the application's local path to your #{RAILS_ROOT}/public directory.
    11. Set Execute Permissions to Scripts and Executables.
    12. Leave this dialog open while you follow the rewrite isapi filter and fcgi extension mapping procedures below.
  3. Add the Fast-CGI (.fcgi) extension mapping to the IIS web site.
    1. Click the "Configuration..." button on the Home Directory tab.
    2. On the "Mappings" tab, click "Add..." to add a new application extension.

    3. Set the extension to ".fcgi" and browse to the isapi_fcgi.dll library, click OK.
      Note: Default is C:\Program Files\Ruby Fast-CGI For IIS-Apache2\IsapiFastCGI\isapi_fcgi.dll
      Make sure "Script Engine" and "Verify that file exists" are checked.

    4. Click OK and then close apply the changes to the web site's properties dialog.
  4. Add Ionic Shade's IsapiRewrite to the IIS web site's Isapi Filters list.
    1. Click the "ISAPI Filters" tab, and click "Add..." to add a new filter.

    2. Name it "REWRITE" and browse to the IsapiRewrite4.dll file, click OK.
      Note: Default is C:\Program Files\Ruby Fast-CGI For IIS-Apache2\IonicIsapiRewrite1.0\IsapiRewrite4.dll

    3. Click Apply and then OK to close the web site's properties dialog.
    4. Note: Your IsapiRewrite4.ini file should look like this one.
  5. Set IIS_WPG read/execute permissions.
    1. Because the rubyw.exe process is run under the NETWORK SERVICE account, you must set execute permissions for the following:
      C:\WINDOWS\SYSTEM32\libfcgi.dll
    2. You must set read/write permissions for your #{RAILS_ROOT}/log directory.
    3. You must set read/write permissions for your #{RAILS_ROOT}/temp directory.
    4. You must set read/write permissions for any #{RAILS_ROOT}/uploads directories.
  6. Set IIS_WPG read permissions in the registry for ADODB.Connection.
      Assuming recent security updates from Microsoft are the cause, if you get the following error:

      Unknown OLE Server: 'ADODB.Connection'
      HRESULT error code: 0x800401f3
      Invalid class string

      The following steps will fix the problem:

    1. Under HKEY_CLASSES_ROOT\ADODB.Connection add read permissions for both the ADODB.Connection and ADODB.Connection.X.X entries where X.X is the current version of MDAC, for either NETWORK SERVICE or IIS_WPG accounts.

    2. Repeat step 1 for HKEY_CLASSES_ROOT\ADODB.Connection.X.X
  7. Restart IIS and test your application.

    The Fast-CGI ISAPI filter uses values stored in the registry. These values can be modified manually. If you change the values, don't forget to restart IIS. Their location is: HKEY_LOCAL_MACHINE\Software\FastCGI\.fcgi\

Action Controller Request.rb (required for IIS)
The built in Action Controller's request.rb's request_uri method doesn't work using Ionic Shades Isapi Rewrite under IIS and Fast-CGI.

Use the following procedure to override action controller's request.rb's request_uri method:

  1. Create a new ruby file called action_controller_request_ext.rb or use the provided one.

    This file needs to be saved in your application's lib folder. ex. "#{RAILS_ROOT}/lib/action_controller_request_ext.rb"

  2. Add the following code snippet and save the file:
    module ActionController
      class AbstractRequest
        # override to fix IIS issues
        def request_uri
          if uri = env['REQUEST_URI']
            (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
          else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
            # remove the script file name from the URI
            uri = env['PATH_INFO'] 
            uri = uri.sub("#{env['SCRIPT_NAME']}", "") unless env['SCRIPT_NAME'].nil?
            # replace the isapi rewrite query string with the original path and query (opnq)
            unless( opnq = env["QUERY_STRING"]).nil? || opnq.empty?
              env["QUERY_STRING"] = opnq.sub("?","&") # overwrite the invalid query string
              uri << opnq.sub("opnq=", "")
            end
            uri
          end
        end
      end
    end
    				
  3. Edit your #{RAILS_ROOT}/config/environment.rb file and append the following snippet:

    This tells rails to use your version of request_uri method. If you plan on deploying your application to other servers or update the rails gem frequently, this keeps you from having to always patch the rails code.

    # Include your app's configuration here:
    require 'action_controller_request_ext.rb'

SQL Server Ruby Driver Configuration
There is one small issue with the built in rails SQL Server connection adapter. If you plan to use Windows Authentication instead of using SQL Authentication, you must apply this patch. I found the best method is to override the Active Record SQL Server Connection Adapter as follows:
  1. Create a new ruby file called active_record_sql_server_connection_adapter_ext.rb or use the provided one.

    This file must be saved into your application's lib folder. ex. "#{RAILS_ROOT}/lib/active_record_sql_server_connection_adapter_ext.rb"

  2. Add the following code snippet and save the file:
    module ActiveRecord
      class Base
        def self.sqlserver_connection(config) #:nodoc:
          require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
          config = config.symbolize_keys
    
          mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
          username = config[:username] ? config[:username].to_s : 'sa'
          password = config[:password] ? config[:password].to_s : ''
    
          if mode == "ODBC"
            raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
            dsn = config[:dsn]
            driver_url = "DBI:ODBC:#{dsn}"
          else
            raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
            database = config[:database]
            host = config[:host] ? config[:host].to_s : 'localhost'
    
            if username.empty? or username == 'SSPI'
              driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};Integrated Security=SSPI;"
            else
              driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
            end
          end
    
          conn = DBI.connect(driver_url, username, password)
          conn["AutoCommit"] = true
          ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
        end
      end
    end
    					
  3. Edit your #{RAILS_ROOT}/config/environment.rb file and append the following snippet:

    This tells rails to use your version of self.sqlserver_connection method. If you plan on deploying your application to other servers or update the rails gem frequently, this keeps you from having to always patch the rails code.

    # Include your app's configuration here:
    require 'active_record_sql_server_connection_adapter_ext.rb'

  4. #{RAILS_ROOT}/config/database.yaml Requirements

    If using Windows Authentication, specify 'SSPI' for username field, password will be ignored. To use SQL Authentication, simply specify the proper credentials for your application to authenticate.

  5. SQL Server Authentication Requirements

    If using Windows Authentication and IIS, you must grant the 'NT AUTHORITY\NETWORK SERVICE' account access to your database. If using Apache2, whichever account you are running the apache.exe service under, you must grant it access to the database.

Notes on Application Based Basic Authentication
Because of the way IIS uses the basic authentication header, the best way to perform rails application level authentication is to pass a custom header field like X_HTTP_AUTHORIZATION and modify your authentication method in your application code to parse it. The header will be passed through (rewritten) as HTTP_X_HTTP_AUTHORIZATION. According to Shane's documentation, you can set up the isapi_fcgi as a filter and change a couple of the registry values but I could never get it to work properly.

Here is a basic implementation for your application.rb application controller:

class ApplicationController < ActionController::Base
  protected
    def require_valid_user
      username, password = parse_authentication_headers
      # put your custom authentication code here (something like the following):
      user = User.authenticate(username, password)
      unless user
        prompt_for_username_and_password
        return false
      end
    end
  private
    def parse_authentication_headers
      # extract authorization credentials
      if request.env.has_key? 'HTTP_X_HTTP_AUTHORIZATION'
        # this is custom authentication header (FCGI under IIS)
        authdata = request.env['HTTP_X_HTTP_AUTHORIZATION'].to_s.split
      elsif request.env.has_key? 'X-HTTP_AUTHORIZATION'
        # try to get it where mod_rewrite might have put it (APACHE)
        authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
      elsif request.env.has_key? 'Authorization'
        # for Apache/mod_fastcgi with -pass-header Authorization
        authdata = request.env['Authorization'].to_s.split
      elsif request.env.has_key? 'HTTP_AUTHORIZATION'
        # this is the regular location
        authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
      end
      if authdata and authdata[0] == 'Basic'
        user,pass = Base64.decode64(authdata[1]).split(':')[0..1]
      end
      return [user,pass]
    end
    def prompt_for_username_and_password
      response.headers['Status'] = '401 Unauthorized'
      response.headers['WWW-Authenticate'] = 'Basic realm="MyRailsServer"'
    end
end
				

References


Shane Caraveo's ISAPI FastCGI
Ionic Shade's ISAPI Rewrite
Ruby For IIS (based on ruby for apache)
Rails on IIS
Rails on IIS Revisited

Questions or comments: Please visit the Ruby On Rails For IIS Fast-CGI website.