Tuesday, October 9, 2007

HOW TO: Rails under IIS with Mongrel and ISAPI_Rewrite3

Update - Dec - 2007

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

Intro

I have been running multiple rails apps under IIS using fast cgi, I was gettting very frustrated (No hair left), It seems to fault one a week. (Think this is related to the size of a post)

The Rails apps are not heavily used each with (~3 full time, several casual users), so a proxy/redirect from IIS to single Mongrel seemed ideal.

IIS will still service up the static/public content, and all other requests are forwarded to Mongrel(s). Using ISAPI_Rewrite3 from Helicon Tech

ISAPI_Rewrite3 is cool it mimics Apache mod_rewrite.

Also I needed to document the install/set-up so here it is.

Install Software

Overview

  • Ruby is installed in C:\ruby
  • Rails applications are under C:\rails
  • Rails application is called "rapp" (Rails application). And in C:\rails\rapp
  • SQL Express is the database.
  • A install CD with all relevant software/gems is available on R:\

Install SQL/Express

Install IIS with a named instance SQL$RAPP this will allow you to shutdown one rails application DB, without affecting others.

C:> cd R:\mssql
C:> dotnetfx.exe  # Install .Net Frame work 2.0 
C:> SQLEXPR32-SP2.EXE # MS SQl Server 2005 Express with SP2, 
# Used Named Instance "RAPP" based on name of rails application
# Used mixed authentication, "sa" password "you-choose"
C:> SQLServer2005_SSMSEE-SP2.msi # MS Sql Server Managment Studio  with SP2

Install Ruby

This is included for completeness

C:> cd R:\ruby
C:> ruby186-25.exe # Install ruby 
# Select Install "Ruby" and "Enable RubyGems"
# Install to "C:\ruby 
# Check C:\ruby\bin to your everyones path !!
# If not added, My Computer -> Properties -> Advanced ->  Environment  Variables -> System Variables
C:> cd R:\ruby\rubygems-0.9.4
C:> ruby setup.rb # Install ruby gems

Install Rails/Mogrel

C:> cd R:\ruby
C:> gem install activesupport-1.4.2 --local
C:> gem install activerecord-1.15.3 --local
C:> gem install actionpack-1.13.3 --localexit

C:> gem install actionmailer-1.3.3 --local
C:> gem install actionwebservice-1.2.3 --local
C:> gem install rails-1.2.3 --local
C:> gem install win32-service-0.5.2-mswin32 --local
C:> gem install gem_plugin-0.2.2 --local
C:> gem install cgi_multipart_eof_fix-2.3 --local
C:> gem install mongrel-1.0.1-mswin32 --local
C:> gem install mongrel_service  --local

Install MSSQL Drivers

C:> cd R:\ruby\ruby-dbi-0.1.1
C:> ruby setup.rb config --with=dbd_ado
C:> ruby setup.rb setup
C:> ruby setup.rb install

Patch Active Record - SQL Server Adapter

SQL 2005 differs from previous versions, it reports null columns as uppercase NULL and you need to patch the sqlserver_adapter.rb

# Patch  sqlserver_adapter.rb (See Rails Ticket #7733) for server 2005
C:> cd C:\ruby\lib\ruby\gems\1.8\gems\activerecord-1.15.3\lib\active_record\connection_adapters
C:> edit sqlserver_adapter.rb
# Line ~282: "=~ /null/ ? nil"   ==> "=~ /null/i ? nil"  # Case insensitive match on the word 

Install RMagick / Faster CSV

NOTE: When require RMagick in your rails app. Use the full name require 'RMagick.rb', as depending on your paths RMagick.so may be included rather than RMagick.rb

C:> cd R:\ruby\RMagick-1.14.1_IM-6.3.0-7-Q8
C:> ImageMagick-6.3.0-7-Q8-windows-dll.exe 
# Install to C:\ruby\ImageMagick-6.3.0-Q8
# Tick [x] Update executable search path.
C:> gem install rmagick-1.14.1-win32 --local
# Note: Documentation is in C:\ruby\lib\ruby\gems\1.8\gems\rmagick-1.14.1-win32\doc\index.html

Install Faster CSV

C:> cd R:\ruby
C:> gem install fastercsv-1.2.0 --local

Install Iaspi Rewriter

UPDATE 30-Oct-2006: You need to install build 29 or above, Prior versions doubled up on cookie data from other sessions. This effectively gave a new session, the session from the last person accessing the site !!! (A bit scary!!)
C:> cd R:\ISAPI_Rewrite3
C:> ISAPI_Rewrite3_00xx.msi
# Install to C:\Inetpub\ISAPI_Rewrite3\

Patch Action Controller - CGI Process

When preforming a re-direct the header is inspected for the real host when request has been forward, however rails does not strip any extra white space.

# Patch cgi_process.rb
C:> cd C:\ruby\lib\ruby\gems\1.8\gems\actionpack-1.13.3\lib\action_controller
C:> edit cgi_process.rb
# Line ~83: forwarded.split(/,\s?/).last  ==> forwarded.split(/,\s?/).last.strip # strip extra spaces/tabs 

Create/Configure Rails Application

Create Rails "Hello World" Application

C:> mkdir C:\rails
C:> cd C:\rails
C:> rails rapp # Create rails app called rapp
C:> cd rapp
C:> ruby script/generate controller hello

Edit C:\rails\rapp\app\controllers\hello_controller.rb. And past in this code.

class HelloController < ApplicationController
   $render_count ||= 0
   def world
     $render_count += 1
     render :text => "<html><body><h1>Hello World</h1>Page rendered #{$render_count} times.</body</html>"
   end
end

Test basic application works with WebBrick

C:> cd C:\rails\rapp
C:> ruby script/server # Run WebBrick to test 
C:> iexplore.exe http://localhost:3000/hello/world 
# Should get hello world page
C:> Ctrl+break # Close WebBrick

Host using mongrel service

Perform the following steps to host rapp using mongrel

NOTE: We will be hosting this rails application under the relative url "/rapp"

NOTE: We only bind to localhost (127.0.0.1), since IIS will eventually forward locally to mongrel. The port chosen is 4004

C:> cd C:\rails\rapp
C:> mongrel_rails service::install -N rails_rapp -a 127.0.0.1 -c C:\rails\rapp -p 4004 -e production --prefix /rapp
# NOTE: adding --prefix /rapp, sets ActionController::AbstractRequest.relative_url_root = "/rapp"
C:> sc config rails_rapp start= auto
C:> net start rails_rapp
# To remove the service use: mongrel_rails service::remove -N rails_rapp

Permissions

Mongrel service will be running as SYSTEM or NETWORK SERVICE, You need to check/give the following permissions.

  • Give FULL permission for C:\rails\rapp\temp and C:\rails\rapp\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 C:\ruby\* and C:\ruby\ImageMagick-6.3.0-Q8
  • Check Read permission Under HKEY_CLASSES_ROOT\ADODB.Connection for both the ADODB.Connection and ADODB.Connection.X.X entries # mssql only

Test using mongrel service

Browse to http://localhost:4004/rapp/hello/world, Note all url's are now relative to rapp, This allows multiple rails apps to be hosted behind iis

C:> iexplore.exe http://localhost:4004/rapp/hello/world 
# Should get hello world page, Rendered for the 1st time 
# Press refresh a few times, See the counter grow.

Now to host behind IIS

Create a virtial directory, so IIS services up the public/static portion of the site, (i.e. C:\rails\rapp\public)

C:> mkdir C:\Inetpub\Logfiles
C:> inetmgr.exe # Change log file folder
  # Goto -> Default Web Site -> Properties -> Web Site Tab -> Logging properties
    Log file directory -> C:\Inetpub\Logfiles

C:> inetmgr.exe # Add in your application
  # Goto -> Default Web Site -> New -> Virtual Directory -> 
    Alias/Name -> rapp
    Directory/Path -> C:\rails\rapp\public
    Permissions -> Read + Scripts + Executables
    
    # Note: Need Execute otherwise proxy/forwarding does not work !!!! 

Test static portion

Browse to http://localhost/rapp/index.html - You should see the standard welcome to rails page

C:> iexplore.exe http://localhost/rapp/index.html 
# Should get the "Rails Welcome aboard" 

Configure ISAPI_Rewrite3 to forward to Mongrel

Confiure ISAPI_Rewrite to forward all http://host/rapp/* to mongrel on http://127.0.0.1:4004/rapp/*. NOTE: We do not forward requests contining a full stop (.), So files like .js and .css etc will be serviced up by iis

C:> C:\Inetpub\ISAPI_Rewrite3\Helicon Manager.exe
    Click -> Edit
    Enter your new rule
        RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Your "C:\Inetpub\ISAPI_Rewrite3\httpd.conf" file should look something like

# Helicon ISAPI_Rewrite configuration file
# Version 3.0.0.23
RewriteEngine on
RewriteBase /
RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1

Rails .htaccess legacy
Remove/Rename the c:\rails\rapp\public\.htaccess file, as this is read by ISAPI_Rewrite but is designed for Apache !!!

C:> rename c:\rails\rapp\public\.htaccess c:\rails\rapp\public\apache.htaccess

Test mongrel/dynamic portion

Browse to http://localhost/rapp/hello/world - You should see the Hello World with counter from last test

C:> iisreset # restart iis
C:> iexplore.exe http://localhost/rapp/hello/world
# Should get hello world page, Rendered for the nth time 

Fininshed

You should now have a working rails application, behind IIS using Mongrel.
To add another application simply nominate a new relative url.

Multiple Applications

When running multiple rails applications, set the session key to be unique for each instance, otherwise you will be trying to share session keys across multiple apps.
C:> Edit \rails\app1\controllers\application.rb
class ApplicationController < ActionController::Base
  # Pick a unique cookie name to distinguish our session data from others'
  session :session_key => '_rapp_session_id'

8 comments:

Anonymous said...

I was about to write up the same thing for the ISAPI Rewrite 3 tool.

It's simple and I get it!

Tom V said...

This is a really handy write up.

I'm currently looking for a way to use ROR for an intranet application that needs to leverage the current user's windows login info.

Scott Gutherie at MS wrote about this in his blog, ScottGu's Blog [ http://weblogs.asp.net/scottgu/archive/2006/07/12/Recipe_3A00_-Enabling-Windows-Authentication-within-an-Intranet-ASP.NET-Web-application.aspx or shorted to http://tinyurl.com/fcpv6 ]

Unfortunately, to get the user's windows authentication token, I need to be in ASP.NET, under IIS, using IE as the client - or so it seems. The approach described here, of using IIS to proxy mongrel, might work, if there's a way to pass the user's authenticated ID along to the ROR application that is running under mongrel.

Any ideas?

Thanks again for your posting.

Tom

Murray Speight said...

Getting the windows user, was trivial (well almost)

Simply change the RewriteProxy to "Add authentication headers"

i.e. edit httpd.conf and add the [A] option.

From by above example.

RewriteProxy rapp/([^.]+)$ http://127.0.0.1:4004/rapp/$1 [A]

then edit the hello world_controller.rb now shows the HTTP_X_ISRW_PROXY_AUTH_USER from the header.

render :text => "<html><body><h1>Hello #{request.env['HTTP_X_ISRW_PROXY_AUTH_USER']} ..."

This will say "Hello DOMAIN\user"

ISAPI_Rewrite3 is great.

Hope this helps you tom,
Cheers Murray

Anonymous said...

Does this work with integrated SQL authentication?

e.g Driver= {SQL Server}; Server=myServerAddress; Database=myDataBase; Trusted_Connection=Yes;

This is useful is roles based authentication and would be a main use for IIS deployment?

Murray Speight said...

To use Windows Authentication, MS SQL with rails.

You need to override the ActiveRecord sqlserver_connection method.

An example of doing this is included with the "Ruby On Rails For IIS Fast-CGI" release.

Simply extract the file "active_record_sql_server_connection_adapter_ext.rb" from the source which is here "http://www.codeplex.com/RORIIS/SourceControl/ListDownloadableCommits.aspx".

Then include "active_record_sql_server_connection_adapter_ext.rb" in your environment.rb.

You may also want to check out dorjem blog on this matter, check out http://dorjem.blogspot.com/2007_02_01_archive.html

Trung Do said...

Hi,

Thank you for nice guide. I have a related problem to ask:

I deployed a Rails application on a Windows Server 2003 machine as
follows:

+ I created two instances of Mongrel at ports 4001, 4002 to serve the
application

+ I set up an Apache instance at port 8080 for balancing load for the
two Mongrels => So I can access my website at URL http://mywebsite:8080/

+ I want to allow users access my website without typing port 8080 in
the URL. However, the server already runs IIS for several other
web-sites at default port 80. Therefore, I used ISAPI Rewrite to forward
requests from IIS (at port 80) to the Apache (and therefore towards the
Mongrels) => I can access the website at URL http://mywebsite/

(To do these steps, I followed the guide in book Deploying Rails
Application - Pragmatic Bookshelf)

Everything seems to work fine until I find out the following problem:

In my application, the user register form page allows user to upload
his/her avatar. But whenever the file size is bigger than ~30KB, the
browser keeps waiting for response from the server until getting timeout
error.

This error also occurs in any submitting form with a little big posted
data.

* Looking at the Apache error log file, I got the following error:
------------------
[error] proxy: pass request body failed to 127.0.0.1:4001
------------------
=> Apache seems to fail to forward request to Mongrel instance

* Looking at the Mongrel log file, I got the following error:
------------------
Error reading HTTP body: # Mongrel seems not to receive fully the data whose length specified in
Content-Length attribute in the request header.

One special thing is that this error just happens when I access the
web-site via IIS, i.e., http://mywebsite. This does not happen when I
try directly with Apache at http://mywebsite:8080 or Mongrel at
http://mywebsite:4001/(4002)

So I think there may be something wrong at the point of forwarding
requests from IIS to Apache using ISAPI Rewrite, maybe related to max
request length or something. But I totally get stuck at that so far.

Please help if you have any suggestion to solve this problem.

Thank you!

Best regards,

.Viet Trung.

Murray Speight said...

Hi Trung Do

Your set-up seems sound.

I have personally had no problems with ISAP rewrite and uploading large images.

Have you checked permissions, maybee ISAP rewrite is trying to write to a temp area on large images, and does not have permission.

Yaroslav Govorunov said...

To run Ruby on Rails on IIS 7+ please try http://www.helicontech.com/zoo/

Google