Scott Hanselman

Announcing: Running Ruby on Rails on IIS8 (or anything else, really) with the new HttpPlatformHandler

February 09, 2015 Comment on this post [22] Posted in IIS | Ruby
Sponsored By

For years there's been numerous hacks and ways to get Ruby on Rails to run on IIS. There's also ways to get Java via Tomcat or Jetty, Go, and other languages and environments to run as well. There's ways to get Node.JS running on IIS using iisnode but that's been node-specific. The blog posts you do find say things like "get Rails to run on IIS in 10 steps" and I'm like JUST TEN?!? Why not 13? Others say "You can deploy Rails under IIS, it's just very difficult and there's not a lot of documentation. You'll need a special Fast-CGI implementation...WELCOME TO HELL."

No longer.

Azure Websites has supported node AND Java (again, Tomcat or Jetty) for a while now, in production and it's very nice and it runs under IIS. How? They've now brought that support to Windows running IIS 8+ with the release of the HttpPlatformHandler. Here's their example on how to get IIS 8+ running Java, easily.

Let's see if this works well with Ruby on Rails as well!

Why is HttpPlatformHandler interesting? Check this from the docs...it means IIS can host anything that runs on Windows now, easily. These things were possible before, but with all kinds of hacks and FastCGI this and that. What's great about HttpPlatformHandler is that it isn't about Rails. It's about any process that's listening on a port. You get all the value of IIS *and* total control of your self-hosting scenario.

The HttpPlatformHandler is an IIS Module, for IIS 8+, which does the following two things:

  1. Process management of http listeners - this could be any process that can listen on a port for http requests.  For example - Tomcat, Jetty, Node.exe, Ruby etc;
  2. Proxy requests to the process that it manages.

To be clear, you can work with Ruby on Rails on Windows and have it host itself with WEBrick locally, but if you're going to go production on Windows you'll want to have IIS and more likely, jRuby in a Tomcat container, similar to using Nginx on linux. What value does IIS provide in a scenario like this? Static file hosting, Reverse Proxy, complex auth that can span multiple apps, languages and frameworks, it monitors and manages your process looking at memory and CPU, crashes, etc.

Running Ruby on Rails on IIS 8 with the HttpPlatformHandler

First make sure you have Ruby on Rails. If you do, skip forward.

I use the http://railsinstaller.org for Windows and go. You'll get Ruby, Rails, Bundler, Sqlite, and TinyTDS. Even SQL Server support. Very kind of them. Another good Rails on Windows on is RailsFTW.

I go to Turn Windows Features On and Off to make sure I have IIS installed as well.

Turn on IIS in Windows Features

Then get the HttpPlatformHandler. You can get it with the Web Platform Installer, or just install it from here: x86/x64

I make a folder for the app I'm going to make. I put it in c:\inetpub\wwwroot\rails but you can move it around if you like.

Make sure your Rails app is an application in IIS

I right-click my folder in IIS Manager and "Convert to Application."

My Rails application in IIS

I run "gem install rails" to make sure I have Rails in the first place. ;) You will if you installed with the RailsInstaller. If you installed with the RubyInstaller, then this will get you Rails.

NOTE: If you have issues with SSL running gem on Windows, you'll need manually to update gem to 2.2.3 as of the time of this writing. I'm not sure why this isn't already done by the installer. The symptom I saw was weird errors on 'bundle install' that was fixed by this.

Installing Rails

Then, from inside c:\inetpub\wwwroot\rails, I ran "rails new helloworld." I ended up moving this folder up. I should have just made the app first, then converted the folder to an app in IIS. Order of operations and all that, eh?

OK, now I'll "rails server" from within c:\inetpub\wwwroot\rails, just to make sure Rails can run under the local WEBrick server. And it does:

Rails on Windows

Now, let's do it under IIS.

I need to make sure there's a web.config file in the same root folder as my Rails app. WHAT?!? Web.config is for ASP.NET, right? Well, no. It's config for any IIS application. You'll need this for Go, Java, PHP, Rails, node, ASP.NET, whatever. IIS can host basically anything.

Lemme add a hello world controller and edit its view. I'll "rails generate controller welcome index" then edit app\views\welcome\index.html.erb for good measure.

I put my Rails app under http://localhost/rails rather than at the root http://localhost so I did need to tell Rails 4 about the fact it's running in a subdirectory with a change to /config.ru, otherwise my routes wouldn't line up.

Rails.application.config.relative_url_root = '/rails'

map Rails.application.config.relative_url_root || "/" do
  run Rails.application
end

Make special note of the paths below AND the encoded " there in the arguments. That's important, because it's a quoted argument passed into the ruby.exe process that IIS will kick off. Note also the %HTTP_PLATFORM_PORT% environment variable reference. That is passed in by IIS and will be a localhost-bound high port.

I also put in foo and bar for theoretical environment variables you might want your Rails app to know about. For example, I might add:

<environmentVariable name="RAILS_ENV" value="production"/>

...when it's time. I put in some standard debug logging there with the "stdout" but you can remove that if you don't want the clutter. Make sure your IISR_ users have write access to the folders if you want to see any logs.

WARNING/DISCLAIMER: This first example is just showing you what's possible. You DON'T want to go to production with the little built-in Ruby WEBrick web server. As Fabio Akita very kindly points out in the comments, and I'll pull his comment out here:

"One thing to be careful with the example using Rails. When running Ruby for Windows, when you run "rails server" it's going to spawn a single process that's mono-threaded, meaning that it can only respond to 1 request at a time. If IIS starts receiving too many requests simultaneously and each request is slow, it's going to generate a long queue until they start timing out.
In Linux we put NGINX to reverse-proxy HTTP requests to Unicorn, or Puma, or Rainbows, or Passenger. They all coordinate multiple Ruby processes (which in Unix, is very cheap as each process reuses memory from it's parent process upon forking - copy-on-write memory). So we can handle simultaneous requests."

You'd want to use JRuby and Tomcat with Puma under IIS for production on Windows.

KEEP SCROLLING FOR EXAMPLES USING JRuby, Trinidad/Tomcat, and Puma! They are farther down the page.

Also, on slower machines when running in development, you might need to up your startupTimeLimit if you are seeing IIS stop your Ruby processes that take to long to startup.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" requireAccess="Script" />
</handlers>
<httpPlatform stdoutLogEnabled="true" stdoutLogFile="rails.log" startupTimeLimit="20" processPath="c:\RailsInstaller\Ruby2.1.0\bin\ruby.exe"
arguments="&quot;C:\RailsInstaller\Ruby2.1.0\bin\rails&quot; server -p %HTTP_PLATFORM_PORT% -b 127.0.0.1">
<environmentVariables>
<environmentVariable name="foo" value="bar"/>
</environmentVariables>
</httpPlatform>
</system.webServer>
</configuration>

Here's a screenshot of Ruby within the SysInternals Process Explorer application. I wanted to show you this so you could see the Process Tree and see who started which process. You can see w3wp (that's IIS) which is a Service, and it's hosting Ruby, running Rails. Make note of the command line arguments as well.

image

And here it is. Ruby on Rails 4 running under IIS8 on my Windows 8 machine.

Ruby on Rails on IIS on Windows

Big thanks to Ranjith Ramachandra (@ranjithtweets) and Andrew Westgarth (@apwestgarth) at Microsoft for the help with the web.config values!

TL;DR

So, basically, to give you the TL;DR version, except at the end. When you have IIS, install HttpPlatformHandler and add a web.config as appropriate and you're all set. Run what you like, passing in the port that IIS will proxy to.

UPDATE: Puma and Trinidad (with Tomcat) on IIS

As pointed out in the comments, it's silly to use WEBrick in Production. Don't'.

I'm told JRuby is the way to go for prod. I was able to install JRuby and both Trinidad (with Tomcat) and Puma and get my HelloWorld running under IIS in an hour.

Here's Trinidad (I'm told Trinidad is out of vogue, however). I did a "JRuby -S gem install trinidad" and was on my way.

Note the JAVA_HOME environment variable setting. I also had to update some security policy files due to a "Illegal key size" error which was a Javaism. Otherwise, it just worked once the paths lined up. If you see problem, it'll be path related or you'll be loading the wrong Java version. Also, experiment but you don't really need to mess with processesPerApplications. If you create a lot of individual processes, Java can take up a lot of memory (300megs or so) per process and you can thrash your system.y

You'll get 10x the perf when running under production, so note I'm explicitly running in the prod environment.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" requireAccess="Script" />
</handlers>
<httpPlatform stdoutLogEnabled="false" processesPerApplication="1" stdoutLogFile="rails.log" startupTimeLimit="20" processPath="C:\jruby-1.7.19\bin\jruby.exe"
arguments="-S trinidad --context /jRubyonRails --env production --dir C:\inetpub\wwwroot\jRubyonRails -p %HTTP_PLATFORM_PORT% ">
<environmentVariables>
<environmentVariable name="JAVA_HOME" value="C:\Program Files\Java\jre1.8.0_31"/>
</environmentVariables>
</httpPlatform>
</system.webServer>
</configuration>

And it works. I was easily able to get 1600+ req/sec on my laptop with minimal effort. I'm sure I could do better with some tuning and a better machine.

JRuby on Rails using Trinidad behind IIS8 with the HttpPlatformHandler

OK, let's swap out Trindad/Tomcat for Puma. I put Puma in the Gemfile and did "bundle exec puma" to test. That worked. Now I need to hook it into IIS. I also had to add gem 'jruby-openssl', :require => false to  my Gemfile to avoid some weird errors.

Basically it was the same thing, as IIS is the reverse proxy and process manager in this case.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" requireAccess="Script" />
</handlers>
<httpPlatform stdoutLogEnabled="false" processesPerApplication="1" stdoutLogFile="rails.log" startupTimeLimit="20" processPath="C:\jruby-1.7.19\bin\jruby.exe"
arguments="-S puma --env production --dir C:\inetpub\wwwroot\jRubyonRails -p %HTTP_PLATFORM_PORT% ">
<environmentVariables>
<environmentVariable name="JAVA_HOME" value="C:\Program Files\Java\jre1.8.0_31"/>
</environmentVariables>
</httpPlatform>
</system.webServer>
</configuration>

I'm using just 1 processPerApplication and I'm getting 1500 req/s easily. I don't know anything about Puma or JRuby but I assume it can do better if I knew how to tune it.

Load testing JRuby on Windows under IIS

I tested without IIS against Puma itself and saw essentially the same results. IIS has minimal overhead that I can't measure in this case and you get its process management and other benefits. If you're having issues with perf doing this kind of stuff, it's unlikely it will be IIS.

JRuby on Rails using Puma with the HttpPlatformHandler

All in all, the HttpPlatformHandler just maybe the reverse proxy you've always wanted for Windows!

Related Links


Sponsor: Big thanks to Infragistics for sponsoring the feed this week! Responsive web design on any browser, any platform and any device with Infragistics jQuery/HTML5 Controls.  Get super-charged performance with the world’s fastest HTML5 Grid – Download for free now!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
February 09, 2015 14:18
I've made the switch to *nix now and wish this was available twelve months ago! Still it's pretty cool, thanks for posting :-)
February 09, 2015 15:23
So it creates subprocess that processes requests. And IIS redirects all requests to sub process port, and then returns response back to the client? Idea itself is nice, but how big is overhead for this?
February 09, 2015 15:42
Sounds cool. Also would be interested to see any stats for overhead penalty.
February 09, 2015 20:13
What is the difference between using this and Application Request Routing?
February 09, 2015 20:26
Sebastiaan, ARR is a software load balancer which distributes requests to multiple servers whereas the httpplatformhandler proxies requests to a process listening on a single IIS Server.
February 09, 2015 20:42
Great post and also kudos to Microsoft for releasing HttpPlatformHandler.

The way I understood it is that it's essentially a reverse-proxy plugin for IIS, right? So it can proxy any HTTP request to any HTTP application server behind it.

One thing to be careful with the example using Rails. When running Ruby for Windows, when you run "rails server" it's going to spawn a single process that's mono-threaded, meaning that it can only respond to 1 request at a time. If IIS starts receiving too many requests simultaneously and each request is slow, it's going to generate a long queue until they start timing out.

In Linux we put NGINX to reverse-proxy HTTP requests to Unicorn, or Puma, or Rainbows, or Passenger. They all coordinate multiple Ruby processes (which in Unix, is very cheap as each process reuses memory from it's parent process upon forking - copy-on-write memory). So we can handle simultaneous requests.

Worst, the default web server that comes bundled with Ruby is Webrick. It's an OK web server, but it's not meant for production-level usage. We use it for development sometimes, but never in production servers.

So the only other solution to run a full Ruby on Rails web application is to use JRuby. With JRuby you will run a full Rails app (with JDBC support for SQL Server, for example) within a solid web server such as Tomcat or JBoss (Trinidad and Torque Box projects). Then you can make HttpPlatformHandler on IIS8 proxy HTTP requests to those servers instead and be able to handle production-level throughputs.
February 09, 2015 21:08
Hi Scott, I post a link on a Brazilian FB Rails group and Fabio Akita, make a response, that quote below is a translate, the original text is Here, and i think he is right. Until we have a better web server for windows in MRI is better to use alternatives to serve Rails on Windows.

Best regards,

Helio Oliveira

It seems to me basically a reverse proxy, which is the same as we do in Linux with Apache or NGINX, ie the IIS8 receives the HTTP request and forwards pro Rails behind him, waiting for the response and returns to browser (so called proxy "reverse"). The problem: In Windows we have no Unicorn or Puma or Rainbows, only Webrick. When run "rails server" in Windows it will rise only 1 process. And IIS8 will pass the requests only to a single process. If they come over, IIS will line up and go releasing one by one. On Linux, with NGINX + Unicorn, can climb several of the Unicorn worker processes and it will balance multiple requests in parallel. That is, if it is really up to something many people behind a IIS8, do not use Ruby for Windows (which will use Webrick). Already invest in making using JRuby that will rise with Trinidad or TorqueBox, which will rise using Tomcat or JBoss and will be able to serve multiple requests in parallel and at the same time, will be able to use JDBC drivers to connect to banks as SQL Server (which should be the reason because they want to run Rails on Windows).
February 09, 2015 21:14
Hahaha, He is quick on the trigger.

Sorry
February 09, 2015 21:53
Fabio - you are totally correct, of course. I'll update the post to make it more clear. Indeed, jRuby would be best for prod.
February 10, 2015 1:28
I was in a shop where we needed to use tomcat and IIS on the same IP and port with host headers so we could do JSP work and ASPX work. I got it working but I never felt very clean after that ;)

Setting up a VS2015 "droplet" VM on azure today and it is asking me if I want things like a bottle or a flask. Not sure what I would use those for at work but if I needed them I would be happy they are there.

You are who again, and we have been assimilated?
February 10, 2015 1:32
Is there a way to let IIS serve the static files out of the public folder of a Ruby on Rails application?
February 10, 2015 2:25
Preetpal, yes, you'd setup the static file handler in web.config and have IIS do the serving before Rails sees it.
February 11, 2015 2:11
Nice I've always found it a pain to get working on Windows.
February 12, 2015 21:11
Cool post! How big is the overhead?
February 12, 2015 21:30
Great! Nice alternative to IIRF (https://iirf.codeplex.com)
February 12, 2015 23:08
This is great! I'm wondering if I can use it as a replacement for iisnode...
WB
February 20, 2015 2:11
I did everything like you described, but my rails app insint' working. It just produces rails.log files in my app folder with the same message: "No adapter found for c:/windows/system32/inetsrv"
Any idea what kind of adapter i need?
March 02, 2015 1:07
Can I be a jerk who just Ctrl+F'd for IISExpress, didn't find a match, and ask if this will work on IISExpress?

I recently moved to a new job where the back-end is Java based, but as their front-end dev who's been spoiled with Web Essentials on Visual Studio, I find myself using the community edition instead of moving over to Sublime or Brackets.

This would be awesome to set a route or a port to get all our Tomcat projects running off IISExpress, since VS doesn't have any non-IISExpress web project templates yet (TypeScript is closest, and still needs IISExpress).

Thanks Scott!
March 18, 2015 15:30
I did like its said in the post, but my app shows


HTTP Error 502.3 - Bad Gateway
There was a connection error while trying to route the request.
Most likely causes:

The CGI application did not return a valid set of HTTP errors.
A server acting as a proxy or gateway was unable to process the request due to an error in a parent gateway.

When browsing the file through IIS manager also i got the same error.

But on running the rails app using webrick its working. Any idea what i did wrong here?
April 14, 2015 23:48
Hey thanks for this writeup! I've been wrestling with this and am wondering how the hell you got this to work. httpPlatformHandler only supports IIS8.0+ which suggests 2012r2, and RubyOneclickInstaller doesn't fully support 2012r2. 2008 only right now- and when I try to install ruby git doesn't install completely on my test case of a shiny new AWS 2012r2 box.

Sounds really promising, however.

I got my app (with libV8 even) running on web brick on 2008r2, but no joy with the XML configs you have shown above (I get errors). I tried to install IIS8.0 limited version refuses to install, as well as the httpPlatformHandler.

What exact platforms did you use?

Thanks again for taking the time to write this up.
April 15, 2015 0:08
Do'h! I grabbed ruby installer instead of rails installer. That is why git didn't install.
May 02, 2015 23:09
Thanks so much for the great article!
I'm getting an error - maybe you can look at my question on Stack Overflow and see if you know the answer?

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.