Ruby on Rails
HowToConfigureIIS7

Overview

IIS7 on Windows Server 2008 includes built-in support for FastCGI. This document will explain how to:
  1. Configure Rails to run directly under IIS, without proxying to another webserver
  2. Configure multiple Rails applications to run in different virtual directories under one site
  3. Configure Rails for use in a shared hosting environment, where end users do not have administrative access to the server.

Versions

The following component versions are being used for testing purposes:

NOTE: Some steps are taken from Mike Volodarsky’s 10 steps to get Ruby on Rails running on Windows with IIS FastCGI, which is specific to IIS6.

Getting Started

  1. Download the Server 2008 ISO and install Windows. When prompted for a installation key, skip this step.
  2. Configure IIS: Start → Run → ServerManager.msc → Roles → Add Roles → Web Server (IIS)
  3. Install Ruby and Rails
  4. Install RubyForIIS.

UPDATE: Replace FastCGI components with VC6 versions.

The version of Ruby used with the One-Click Installer is compiled using Visual C++ 6.0, and depends on MSVCRT.DLL. For compatibility, extensions must be compiled with the same version.

The FastCGI components installed by RubyForIIS are compiled with Visual C++ 7.1, and depend on MSVCR71.DLL. This version discrepancy can cause access violations when memory is allocated by one DLL, and freed by another. To fix this, we need to use a version of FastCGI compiled against VC6.

Download fcgi-vc6.zip and overwrite the following files:

Thanks to Luis Lavena and Rick James for providing updated compiled versions. Source can be found here.

Mapping requests to the FastCGI handler

For demonstrative purposes, open a command prompt, and create a Rails app as follows:


cd c:\inetpub\wwwroot
rails myapp
cd myapp
ruby script\generate controller test index

Open Internet Services Manager via Start → Run → inetmgr. Stop the Default Site, and create a new site using the path c:\inetpub\wwwroot\myapp\public.

Click on your site → Handler Mappings → Add Module Mapping.

Under “Request Restrictions”, keep the defaults: invoke the handler even if it isn’t mapped to a file or folder, and accept all HTTP verbs.

Click OK. When prompted to create a FastCGI application for this executable, click Yes.

Background: IIS configuration files

IIS7 configuration is stored in a tiered hierarchy of XML configuration files:

Similar to .htaccess files, settings are inherited by default, but can be overridden as scope narrows. Override permissions can be configured as well; this will come in handy later, when we want to let non-administrative users run their own Rails apps.

By adding a handler through the GUI, we created configuration settings in the following files:

c:\windows\system32\inetsrv\config\applicationHost.config


<fastCgi>
<application fullPath="c:\ruby\bin\ruby.exe" arguments="c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development" />
</fastCgi>

c:\inetpub\wwwroot\myapp\public\web.config


<handlers>
<add name="rails-myapp" path="*" verb="*" modules="FastCgiModule" scriptProcessor="c:\ruby\bin\ruby.exe|c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development" resourceType="Unspecified" />
</handlers>

Testing your application

You’re ready to go. Hit http://localhost/test and it should render your test controller. The first request will take a while to load up as the ruby process spawns, but subsequent requests should be much faster while ruby is still in memory.

Handling static files

With FastCGI, Rails should behave like a 404 handler: dispatch.fcgi should only be invoked when the webserver fails to find a physical file at the request path. This allows caching to work, and ensures that asset files (stylesheets, javascripts, images) don’t get routed to Rails.

The default configuration in IIS7 doesn’t quite accommodate this behavior. By default, there is a low priority handler that maps all requests to StaticFileModule, if they cannot be handled by anything else. StaticFileModule either serves the physical file, or returns a 404 if it can’t be found. We need it to still serve existing files, but let Rails handle everything else.

The best solution is Rick James’ excellent Rubynator module. This post contains source code, and instructions for compiling and deploying the DLL. The attached zip file also includes a compiled x86 DLL, that I was able to deploy as follows:

  1. Extract rubynator.dll to c:\windows\system32\inetsrv
  2. In inetmgr, click Modules → Configure Native Modules → Register. Set the name to “RubynatorModule” and the path to “c:\windows\system32\inetsrv\rubynator.dll”.

Using this module, your web.config should look something like the following:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <system.webServer>
  <handlers>
   <clear />
   <add name="rails-myapp" path="*.rb" verb="*" modules="FastCgiModule" scriptProcessor="c:\ruby\bin\ruby.exe|c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development" resourceType="Unspecified" />
   <add name="Static" path="*" verb="*" modules="StaticFileModule" resourceType="File" />
  </handlers>
  <modules>
   <add name="RubynatorModule"/>
  </modules>
 </system.webServer>
</configuration>

Note the following:

The module works by appending “index.rb” to a temporary version of the request URL, when a static file cannot be found. This sends non-existant requests to the “(asterisk).rb” handler, and lets StaticFileModule serve everything else. Note that this doesn’t affect the request URL that Rails will see. You do not need to modify routes or change any other behavior when using this. The modified request with “index.rb” is only used by IIS for handler mappings.

There are a few simpler methods for static file handling that may be useful for initial testing. These are discouraged for production use, as they will mishandle Rails requests that contain a period, or static requests that don’t.

One such option is to set the path for the static handler to “(asterisk).(asterisk)”, which will catch your javascripts, css and images.

A slightly cleaner option is to add explicit handlers to your web.config for each static file type you are using:

<handlers>
<add name="static-css" path="*.css" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="static-js"  path="*.js"  verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="static-png" path="*.png" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="static-jpg" path="*.jpg" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="static-gif" path="*.gif" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="rails-myapp" path="*" verb="*" modules="FastCgiModule" scriptProcessor="c:\ruby\bin\ruby.exe|c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development" resourceType="Unspecified" />
</handlers>

Setting the environment

When IIS spawns ruby.exe, we have configured it to pass in the following arguments:

“c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development”

In this case, “development” will show up in ARGV[0]. To set the environment, I am using the following line in \config\environment.rb:


ENV['RAILS_ENV'] ||= ARGV[0] || 'development'

You can now pass a different environment in the arguments to ruby.exe, and change environments without changing code.

Caveat user: I don’t know if this is the most Railsy way to do this, but it works.

Configuring Rails for non-administrative users

Using Virtual Directories

In shared environments, you may want to run multiple Rails apps along side PHP, ASP, ASP.Net, or other scripting languages. To do this, we need to isolate your Rails handler mapping to a particular subdirectory.

In Internet Services Manager, choose “Advanced Settings” for your site, and change the physical path from c:\inetpub\wwwroot\myapp\public to c:\inetpub\wwwroot. Now, right-click on the “myapp” folder, and select “Add Virtual Directory”. Set the alias to “myapp” and the physical path to “c:\inetpub\wwwroot\myapp\public”.

Doing this accomplishes two things:

  1. Now that myapp is a virtual directory, IIS will recognize a web.config in that directory. You can define different handler mappings in different web.config files. The web.config in your public directory maps requests to Rails; you can create another web.config in c:\inetpub\wwwroot with your standard script mappings, or add another Rails app and map it to a different dispatch.fcgi file.
  2. By mapping the physical path ”/myapp/public” to the virtual path ”/myapp”, we closed a security hole. Normally, you don’t want the root of your Rails app to be inside a web root. This opens up sensitive files like /config/database.yml to public view. By giving our virtual directory the same name as our physical Rails root, we’ve prevented files above the public directory from being exposed.

Now that your Rails app is not running at the web root, you will need to tweak your routes and asset paths accordingly.

Update routes.rb with a variable for your top directory:

ActionController::Routing::Routes.draw do |map|
  basedir = '/myapp/'
  map.connect basedir + ':controller/:action/:id'
end

Update environment.rb with your asset path, if needbe:


ActionController::Base.asset_host = '/myapp'

Overriding configuration options

To enable hands-off administration of multiple Rails apps to end users, a few steps must be taken by the host.

By default, end users can configure handler mappings in their web.config files, and route requests to any executable. For security reasons, the list of safe executables must be defined in the global applicationHost.config file. Each Rails app calls ruby.exe with a different argument string, so each needs its own entry in applicationHost.config.

To add Rails apps, end users will need to be able to register these. They also need the ability to add virtual directories, which are defined in the same file.

In shared environments, this should preferably be accomplished through a control panel, as a layer of abstraction and control over the applicationHost.config file. Microsoft offers a number of tools to automate administration in IIS7, such as AppCmd.exe.

Another option is to allow the FastCgi section and/or virtual directory definitions to be overridden at the web.config level. If this is done, sufficient access control needs to be put in place to prevent end users from accessing files that belong to other users.

End users without server access will need to develop on a seperate machine, and then sync files to the server. This will allow them to use generators, tests, and freeze Rails to the desired version.

Configuring FastCGI resource limits

Each FastCGI application has a number of resource limits in place by default. These limits can be configured by defining them explicitly in your applicationHost.config. For example:


<application
fullPath="c:\ruby\bin\ruby.exe" 
arguments="c:\inetpub\wwwroot\myapp\public\dispatch.fcgi development" 
maxInstances="4" 
requestTimeout="90" 
activityTimeout="30" 
idleTimeout="300" 
instanceMaxRequests="200" 
protocol="NamedPipe" 
queueLength="1000" 
/>

Other Resources