Announcing: Running Ruby on Rails on IIS8 (or anything else, really) with the new HttpPlatformHandler
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:
- 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;
- 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.
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.
I right-click my folder in IIS Manager and "Convert to Application."
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.
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:
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=""C:\RailsInstaller\Ruby2.1.0\bin\rails" 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.
And here it is. Ruby on Rails 4 running under IIS8 on my Windows 8 machine.
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.
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.
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.
All in all, the HttpPlatformHandler just maybe the reverse proxy you've always wanted for Windows!
Related Links
- HttpPlatformHandler Configuration Reference
- HttpPlatformHandler Forums
- Uploading and running a custom Java Website to Azure
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.
About Newsletter
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.
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).
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?
Any idea what kind of adapter i need?
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!
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?
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.
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.