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.
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.
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.
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>
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.
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:
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>
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.
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:
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'
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.
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" />