Tuesday, May 29, 2007

How to: Run Multiple Rails Apps under IIS using fast Cgi

I have found iis/aspi/fast cgi to be un-reliable, seems to crash once a week, I think it is related to a specific size of a post, But have not had the time to track it down

Update - Oct - 2007

Thanks this page seems to be quite popular

A much better/reliable solution is to use IIS with Mongrel behind it,
Check out HOW TO DEPLOY: Rails under IIS with ISAPI_Rewrite3/Mongrel

Update - Dec - 2007

I have updated this entry for rails 2.0
Check out Rails v2.0 with IIS, ISAPI_Rewrite3, Mongrel

Disclaimer

First off I dont recommend it, But if you must as dictated by an outside source here are the steps.

There are severl source's of information around but none that give and how/who with step-step details.

After accessing various sources, I finally got IIS to work "reasonably reliably", PS: Included are tips for trouble shooting permissions issues.

Background - How do you get IIS to talk to rails

When IIS sees an url like http://host/something.asp?search=abc, It looks up and sees if there is an application (dll/exe) associated with the extension .asp, if there is then it passes the request to the application associated with the extension.

So in order to run a ruby application behind iss, we need to associate a extension with "rubyw.exe" for each rails app, the extension can be anything, lets call this ".rap1" (Ruby Application 1).

Below details that steps to do this

STEP 1 - Rewrite The Url

We need to give IIS a url that looks something like http://host/something.rap1?information-for-rails-app,

But Rails generates nice looking urls, e.g. http://host/app1/controller/method, IIS allows you to re-write URL's,

So we take http://host/app1/controller/method?etc=1 and convert it to http://host/something.rap1?opnq=/controller/method?etc=1

To do this we grap "Ionic's Isapi Rewrite for IIS" (details below)

STEP 2 - Rewrite it back for Rails

But the Rails app is expecting "/controller/method?etc=1" not "opnq=/controller/method?etc=1", So you need to re-write it back in to a nice url inside rails.

To do this we patch ActionController::AbstractRequest.request_uri (see app1/lib/action_controller_request_ext.rb)

STEP 3 - app prefix the Url

Since we will be running multiple apps, we need to prefix the urls rails generates with /app1, to distinguish each application (see app1/lib/environment.rb, ActionController::AbstractRequest.relative_url_root = "/app1")

Background - Running Ruby from IIS

OK lets assume we have the url's ok, Next step is to actually run ruby (rubyw.exe) to get the app to work.

We could run rubyw directly as a cgi, but this would mean for each request, rails is started run and closed, VERY VERY Slow.

So we take "Shane Careveo's Fast CGI" dll which keeps the rails app running and simply feeds it new requests

STEP 4 - Map your extension

Using IIS Map the .rap1 extension to call the isapi_fcgi.dll. (which will call rubyw)

STEP 5 - Configure fcgi to run Ruby

Tell the isapi_fcgi.dll what ruby application to run when it gets called with a .rap1 extension, (details below)

You will also need to install ruby code, to deal with how isapi_fcgi disptaches. (see dispatch.fcgi)

STEP 6 - Permission

Fix up permissions as ruby will be run as a limited user.

Misc Notes

You can replace "opnq" in the url, with anything you like, I kept it the same as used by RORIIS, also the "something" can be any name you want, I called it fastcgi, but all we need is for IIS to look at the extension .rap1 and call isapi_fcgi.dll that in turn runs ruby!!!

Install Ruby/Rails MSSQL Software

The boring bit, This section you can skip, But was included for completeness

Install MSSQL
> dotnetfx.exe  # Install .Net Frame work 2.0 
> SQLEXPR32-SP2.EXE # MS SQl Server 2005 EXPRESS 
  # Install to "C:\Program Files\Microsoft SQL Server 2005 EXPRESS"
  # Used Named Instance "SQLEXPRESS"
  # Used mixed authentication, "sa" passsword "yourchoice"
> SQLServer2005_SSMSEE.msi # Install MS Sql Server Managment Studio
Install Ruby + Gems
> mkdir C:\ruby
> run ruby185-21.exe # Install ruby 
  # Select Install "Ruby" and "Enable RubyGems"
  # Install to "C:\ruby 
  # Check C:\ruby\bin to your everyones path !!
  
  # download rubygems, un-zip and run setup.rb
> ruby setup.rb # Install ruby gems
Install Rails
> cd C:\ruby
> gem install --include-dependencies rails # Wait may take a while !!!
> gem install --include-dependencies win32-service # pick the most recent mswin32 !!
# gem install --include-dependencies mongrel # pick the win32 pre-built
# gem install --include-dependencies mongrel_service  
Install MSSQL Drivers
# download ruby-dbi, un-zip and run setup.rb
> ruby setup.rb config --with=dbd_ado
> ruby setup.rb setup
> ruby setup.rb install
# Patch  sqlserver_adapter.rb (See Rails Ticket #7733)
> cd C:\ruby\lib\ruby\gems\1.8\gems\activerecord-1.15.3\
                  lib\active_record\connection_adapters
> edit sqlserver_adapter.rb
# Line 28x: "=~ /null/ ? nil"   ==> "=~ /null/i> ? nil"  
#    Case insensitive match on the word null
Install other gems (e.g. RMagick / Faster CSV)
# download ImageMagick and run setup.rb
> ImageMagick-6.3.0-7-Q8-windows-dll.exe 
# Tick [x] Update executable search path.

# download rmagick gem and install
> gem install rmagick --local
# Note: Documentation is in C:\ruby\lib\ruby\gems\
#    1.8\gems\rmagick-1.14.1-win32\doc\index.html

# download fastercsv gem and install
> gem install fastercsv --local
Create Rails Application and check operation
Create a basic rails app, for testing. I assume c:\rails is where your rails apps will be placed
> mkdir C:\rails
> cd rails
> rails app1 # Create rails app called app1
> ruby script/server # Run WebBrick to test 
> iexplore.exe http://localhost:3000 
  # Should get welcome page
> iexplore.exe http://localhost:3000/controller/etc
  # You should see routine error, no route found... 
  # exception from the from the rails app  
> Ctrl+break
> exit 

The Details

The original source for set-up is as follows. Thanks to all.

"Ruby On Rails For IIS Fast-CGI" (This contains all the files menthoned)

NOTE: If did not install "ROR4IISFCGI_1.0.5.exe", As the install is for a single rails application, Simply Open the file with say 7-Zip and extract. I used this file simply as the source. For each step I have detailed only the final destionation folder

The set-up process detailed below, assumes the folllowing.

  • Ruby is installed in c:\ruby
  • Your application is called "app1" and resides in c:\rails\app1, The extension nominated for is .rap1
  • Iis has C:\Inetpub as its base
  • Windows is C:\Windows

Install/Configure IaspiRewriter

The following files are installed/edited
Inetpub
Inetpub\IsapiRewrite4.dll
Inetpub\IsapiRewrite4.ini # Copied and Edited
rails
rails\app1
rails\app1\lib
rails\app1\lib\action_controller_request_ext.rb
rails\app1\config\environment.rb # Edited  
rails\app1\config\routes.rb # Edited  
Rewrite The Url
# copy/install Inetpub\IsapiRewrite4.dll, Inetpub\IsapiRewrite4.ini
> inetmgr.exe # Add url rewriter 
  # Goto:  Default Web Site -> Properties -> ASPI Filters -> Add 
    Name: IsapiRewrite4
    Exe: C:\Inetpub\IsapiRewrite4.dll  
> cd C:\Inetpub
> edit IsapiRewrite4.ini
  # Add the following rule to take urls with /app1 to call fastcgi.rap1 
  RewriteRule ^/app1(/[^.]+)$ /fastcgi.rap1?opnq=$1
  # Note: urls, with a '.' in them are not mapped. 
  # This is cool, because iis will handel serving up the the 
  # public images/css etc (see later)
> edit routes.rb  # If using Web Services. Change the .wsdl to _wsdl 
  as now cannot have a "." in a url destined for rails
  # change ':controller/service.wsdl' => ':controller/service_wsdl'

Rewrite it back for Rails
# copy/install rails\app1\lib\action_controller_request_ext.rb
> cd C:\rails\app1\config
> edit environment.rb
  # Add the following line to the section titled 
  # "Include your application configuration below"
  require 'action_controller_request_ext.rb'
app1 prefix the Url
> cd C:\rails\app1\config
> edit environment.rb
  # Add the following line to the section titled 
  # "Include your application configuration below"
  ActionController::AbstractRequest.relative_url_root = "/app1" 
      if ENV['RAILS_ENV'] == 'production'

Install/Configure FastCGI

The following files are installed/edited
Inetpub
Inetpub\isapi_fcgi.dll
Inetpub\wwwroot
Inetpub\wwwroot\fastcgi.rap1 # Created manually
rails
rails\app1
rails\app1\FastCGI.reg # Created manually 
rails\app1\public
rails\app1\public\dispatch.fcgi
ruby
ruby\lib
ruby\lib\ruby
ruby\lib\ruby\site_ruby
ruby\lib\ruby\site_ruby\1.8
ruby\lib\ruby\site_ruby\1.8\fcgi.rb
ruby\lib\ruby\site_ruby\1.8\i386-msvcrt
ruby\lib\ruby\site_ruby\1.8\i386-msvcrt\fcgi.so
Windows
Windows\System32
Windows\System32\libfcgi.dll
Windows\System32\msvcp71.dll # if not already present
Windows\System32\msvcr71.dll # if not already present
Configure fastcgi to run Ruby

Place settings in the register to tell isapi_fcgi which ruby app to run based on the file extension it is called as

> copy files as indicated above ...
> echo "# fastcgi" > C:\Inetpub\wwwroot\fastcgi.rap1 # Simply a dummy file
> edit/create C:\rails\app1\FastCGI.reg
# Place the following in the file. 

# These registry settings tell isapi_fcgi.dll what rails 
# app to run based on what file extension it is called with

[HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI]
[HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI\.rap1]
@=""
"BindPath"="Rails-FCGI-app1"
"AppPath"="C:\\ruby\\bin\\rubyw.exe"
"Args"="C:\\rails\\app1\\public\\dispatch.fcgi"
"Environment"=hex:52,41,49,4c,53,5f,45,4e,56,3d,70,72,6f,64,75,63,74,69,6f,6e,0d,0a,00
"StartServers"=dword:00000003
"IncrementServers"=dword:00000001
"MaxServers"=dword:00000006
"MaxProcMem"=dword:00000064
"Timeout"=dword:00000258

# The key "Environment" contains "RAILS_ENV=production\r\n\0" 

> cd C:\rails\app1\
> FastGGI.reg # Load details into the widows registry
Configure/Map your extension IIS

We will Map a Virtual directory to our application, Map the extension .rap 1 to our application

> inetmgr.exe # Add in your application to Virtual dir 
  # Goto:  Default Web Site -> New Virtual Directory -> 
    Name -> app1
    Path -> C:\rails\app1\public
    Permissions -> Read
>  netmgr.exe # Add in your application extension .rap1
  # Goto:  Default Web Site -> Properties -> Home Directory 
         -> Configration -> Mapping -> Add
    Executable -> C:\Inetpub\isapi_fcgi.dll
    Extension -> ".rap1"  # with the "."
    Script Engine -> Ticked
    Check the File Exists -> Ticked
  # If the OK button is gray, (bug in IIS 5.1)
      click on the Executable File Name a 2nd time 
  # Ensure Cache ISAP Extensions is Ticked 
Configure Permissions (the messy bit)

Your app wont run yet, as rubyw will be running as a restricted user. Perform the following steps. We will set-up a group called "Rails" and assign the IIS process user "IWAM_computername" to this group.

> compmgmt.msc
  # Goto: Local Users and Groups -> New Group -> Rails
  # Add new Rails group (with no members)   
> compmgmt.msc
  # Goto: Local Users and Groups -> IWAM_computername -> 
  #       Properties -> Member Of -> Add -> Rails

Configure iis to run application's as the local user "IWAM_computername". This makes managing permissions simpler.

> inetmgr.exe 
  # Under Server 2003+ 
    # Goto:  Application Pools -> 
       DefaultAppPool -> Properties -> Identiry 
    # Change to "IWAM_computername" 
  # Under XP 
    # Goto:  Default Web Site  ->  Properties ->
         Home Directory -> Identiry 
    # Application protection to "Medium (Pooled)"" 

Server 2003+ only, Configure IIS to accept the dll's and rubyw.exe application

# Under Server 2003+ (Only)
> inetmgr.exe  # Set permissions on applications
  # Goto:  Web Server Extension -> Add Web Server Extension 
    Name: Rails
    Files: Inetpub\IsapiRewrite4.dll, 
           Inetpub\isapi_fcgi.dll, 
           Windows\System32\libfcgi.dll, 
           C:\ruby\bin\rubyw.exe
    Status: Allowed
Set premissions on the required files/dirs for the "Rails" group

Dont forget this step otherwise your application will not run !!!

  • Give FULL permission for C:\rails\app1\temp and C:\rails\app1\log
  • Give FULL permission to TEMP (C:\WINDOWS\TEMP)
  • Give FULL permission to any other folders your rails app1 writes to (e.g C:\rails\app1\public\attachments )
  • Check Read/Execute permissions for all the .dll's menthoned above and rubyw.exe
  • Check Read permission Under HKEY_CLASSES_ROOT\ADODB.Connection for both the ADODB.Connection and ADODB.Connection.X.X entries # mssql only
  • Check Read permission Under HKEY_LOCAL_MACHINE\SOFTWARE\FastCGI # this should be ok

Test It

Perform the following steps to test it works.

Ist check your app still works with WebBrick (see above Installing Ruby etc)

> iisreset.exe # Re-start iis
> iexplore.exe http://localhost/app1/controller/etc
# You should see routine error, no route found... 
# If so you have an exception Message from the Rails app, cool !!! 
# If you get fast cgi error or page not found, 
#        check out trouble shooting. 

Configure Your Second Application

Simply repeat the steps above (that relate to a specific application) for your application. Assgning your own name and extension.

Simply change "app1" to your application name and".rap1" to your nominated extension

I would not recommend using .asp/.aspx extension, quite yet, mind you with ruby/rails going so well, it wont be long before the .aspx extension will become un-used. :-)

NOTE: Do make sure you change the "BindPath"="Rails-FCGI-app1" in the registry for fast CGI for each application, otherwise you may end up with one app's url going to another.

Trouble shooting

Some trouble shooting tips.

First off, Does rubyw start within iis.
  • Grab "Process explorer" from sysinternals
  • run "procexp.exe"
  • Right click headings and choose select columns, Add "User Name"
  • Reset iis. (iisreset.exe)
  • navigate to http://localhost/app1/controller/etc
  • watch the dllhost.exe within svchost.exe,
    • does rubyw.exe appear then disappear, If so possibly a permissions thing, does the rubyw.exe user, have permissions to all the files.
    • It does not appeara Check the fcgi set-up.
Try running your app as a restricted user.

Try the following to run your app as a restricted user. It might highlight some things

  • Create a user called IWAM_TEST (give it a password)
  • Remove IWAM_TEST from the Users group
  • Add IWAM_TEST to the Rails group
  • Now Run your app as IWAM_TEST using WebBrick
  • > runas /user:IWAM_TEST cmd 
    # You should get another cmd window as this user, 
    > env 
    # Look at the env, look ok. 
    > cd \rails\app1
    > ruby script/server
    # Look at the output, Does it say "unable to access log file" 
    # If so fix permissions
    
    Navigate to http://localhost:3000/controller/etc
    
    # Any more messages
    > Ctrl+break # break out of WebBrick
    > exit  
    
    

Conclusion

I hope this write up helps some. It helped me.

I know there is not much documentation on multiple apps and IIS.

I have done it, but think IIS -> Apache -> Mongrel would be a better choice.

All comments welcome

An Update

There is one small change you need to make to cgi.rb to make fastcgi more reliable. Check out Running Rails Apps More Reliability under IIS

1 comment:

Murray Speight said...

An update by the author, there is one small change you need to make to cgi.rb to make fastcgi more reliable.
Check out Running Rails Apps More Reliability under IIS

Google