Scott Hanselman

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

February 9, '15 Comments [23] 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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Blocking Image Hotlinking, Leeching and Evil Sploggers with IIS Url Rewrite

June 3, '13 Comments [60] Posted in IIS
Sponsored By

I recently discovered that a blog called (seriously) "Google Chrome Browser" was reblogging my site. (It of course has NO relationship to Google or the lovely folks on the Chrome team.)

This is a splog or "spam blog." It's less of a blog and more of a 'suck your feed in and reblog it.' Basically every post is duplicated or sucked in via RSS from somewhere else.  I get this many times a week and have for years.

However, this particular site started showing up ahead of mine in searches and that's not cool.

You evil bastards.

Worse yet, they have almost 25k followers on Twitter. I've asked them a few times to stop doing this, but this time I got tired of it.

They're even 'hotlinking' my images, which means that all my PNGs are still hosted on my site. When you visit their site, the text is from my RSS but I pay for the images bandwidth. The irony of this is thick. Not to mention my copyright notice is intact on their site. ;)

When an image is linked to from another domain the HTTP_REFERER header is populated with the location that the image is linked from. That means when my web server gets a request for 'foo.png' from the Google Chrome Browser blog I can see the page that asked for that image.

For example:

Request URL:http://www.hanselman.com/blog/content/binary/Windows-Live-Writer/How-to-run-a-Virtual-Conference-for-10_E53C/image_5.png
Request Method:GET
Referer:http://google-chrome-browser.com/penny-pinching-cloud-how-run-two-day-virtual-conference-10

Because this differentiates the GET request that means I can do something about it. This brings up a few important things to remember in general about the web that I feel a lot of programmers forget about:

That said, I want to detect these requests and serve a different image.

If I was using Apache and had an .htaccess file, I might do this:

RewriteCond %{HTTP:Referer} ^.*http://(?:www\.)?computersblogsexample.info.*$
RewriteHeader Referer: .* damn\.spammers

RewriteCond %{HTTP:Referer} ^.*http://(?:www\.)?google-chrome-browser.*$
RewriteHeader Referer: .* damn\.spammers

#make more of these for each evil spammer

RewriteCond %{HTTP:Referer} ^.*damn\.spammers.*$
RewriteRule ^.*\.(?:gif|jpg|png)$ /images/splog.png [NC,L]

Since I'm using IIS, I'll do similar rewrites in my web.config. I could do a whitelist where I only allow hotlinking from a few places, or a blacklist where I only block a few folks. Here's a blacklist.

<system.webServer>
<rewrite>
<rules>
<rule name="Blacklist block" stopProcessing="true">
<match url="(?:jpg|jpeg|png|gif|bmp)$" />
<conditions>
<add input="{HTTP_REFERER}" pattern="^https?://(.+?)/.*$" />
<add input="{DomainsBlackList:{C:1}}" pattern="^block$" />
<add input="{REQUEST_FILENAME}" pattern="splog.png" negate="true" />
</conditions>
<action type="Redirect" url="http://www.hanselman.com/images/splog.png" appendQueryString="false" redirectType="Temporary"/>
</rule>
</rules>
<rewriteMaps>
<rewriteMap name="DomainsBlackList" defaultValue="allow">
<add key="google-chrome-browser.com" value="block" />
<add key="www.verybadguy.com" value="block" />
<add key="www.superbadguy.com" value="block" />
</rewriteMap>
</rewriteMaps>
</rewrite>
</system.webServer>

I could have just made a single rule and put this bad domain in it but it would have only worked for one domain, so instead my buddy Ruslan suggested that I make a rewritemap and refer to it from the rule. This way I can add more domains to block as the evil spreads.

It was important to exclude the splog.png file that I am going to redirect the bad guy to, otherwise I'll get into a redirect loop where I redirect requests for the splog.png back to itself!

The result is effective. If you visit their site, I'll issue an HTTP 307 (Moved Temporarily) and then you'll see my splog.png image everywhere that they've hotlinked my image.

Not cool, splogger, not cool.

If you wanted to change the blacklist to a white list, you'd reverse the values of allow and block in the rewrite map:

<rewriteMaps>
<rewriteMap name="DomainsBlackList" defaultValue="block">
<add key="google-chrome-browser.com" value="allow" />
<add key="www.verybadguy.com" value="allow" />
<add key="www.superbadguy.com" value="allow" />
</rewriteMap>
</rewriteMaps>

Nice, simple and clean. I don't plan on playing "whac a mole" with sploggers as it's a losing game, but I will bring down the ban-hammer on particularly obnoxious examples of content theft, especially when they mess with my Google Juice.

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Redirecting ASP.NET Legacy URLs to Extensionless with the IIS Rewrite Module

May 10, '13 Comments [19] Posted in ASP.NET | IIS
Sponsored By

ASP.NET has included support for "friendly URLs" for a while now. ASP.NET MVC has always supported friendly URLs and more recently, so has Web Forms. That means if you don't want to have the .aspx extension, you certainly don't have to.

However, there's a LOT of existing legacy apps out there as well as apps that you may not have full control over. For example, there's a site that I want to influence but it's got dozens (hundreds) of links to foo.html and bar.html existing pages.

Legacy (n): A super-fancy way of saying "already exists."

What I want to do is kind of sloppy and I'm doing it for aesthetic reasons. I'll hopefully get around to updating the site's links later and know that future links will be extensionless. But look at me, I'm justifying why I'm doing this, Dear Reader. You of all people know that sometimes you just gotta do something just because ya gotta Get It Done™©.

I want to:

  • redirect existing GETs to a /foo.html to /foo
    • Redirects are external
  • but, keep rewriting /foo to the underlying /foo.html so it handles the request
    • Rewrites are internal

I can do all this within my web.config using the IIS Url Rewrite Module.  I can do this with ANY file type that IIS can handle, meaning this isn't an ASP.NET-specific thing. This all happens well before your application gets involved. You'll note I did a similar thing with a PHP app running under IIS just last month.

Here's what my web.config looks like. Note that since I have Azure (or in just IIS 7+ and the Rewrite module) I just added this file. There was no configuration needed. The same would apply to any existing site. Be aware that sometimes super-"greedy" rewrite or redirect rules can end up grabbing ahold of your CSS or JS so you'll want to be aware if something odd happens.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="extensionless" stopProcessing="true">
<match url="(.*)\.html$" />
<action type="Redirect" url="{R:1}" redirectType="Permanent" />
</rule>
<rule name="removeextension" enabled="true">
<match url=".*" negate="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{URL}" pattern="(.*)\.(.*)" negate="true" />
</conditions>
<action type="Rewrite" url="{R:0}.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

This stuff is hard to write, though.

SIDE NOTE: My RegEx expert Ruslan points out that the final Action could be simplified slightly like this:

<add input="{URL}" pattern="\." negate="true" />

This stuff is also hard to test. The IIS Rewrite module has a great UI for IIS that will write a lot of these rules for you and let you test them interactively:

URL Rewrite module in IIS

Hope this helps. I wrote this post (and bookmarked it) for myself because I am always googling around for this particular rule to remind myself. Now I'll search my own blog. ;) Reason #64 to blog, friends.


SPONSOR: Big thanks to the feed sponsor this past week, Ext.NET (seriously, check out their demos, really amazing stuff!) - Quickly build modern WebForm and MVC (including RAZOR) Apps for ASP.NET. Free pancake breakfast with all purchases!

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Changing ASP.NET web.config inheritance when mixing versions of child applications

March 26, '13 Comments [19] Posted in ASP.NET | Bugs | IIS
Sponsored By

Mixed Application Pools

My blog and all the sites in and around it are a mix of .NET 2.0, 3.5 and 4. This blog engine is currently .NET 3.5 and runs at http://hanselman.com/blog, but the application at http://hanselman.com/ (the root) is .NET 4.

You can happily mix and match applications across .NET versions on a single IIS instance. You can see how mixed my system is in the screenshot at right there.

However, things got messy when I changed the parent / application to .NET 4, but kept the child /blog as .NET 3.5 (the 2.0 CLR). I got lots of errors like

  • Unrecognized attribute ‘targetFramework’. Note that attribute names are case-sensitive. The targetFramework attribute was inherited from the root .NET 4 Web.config file in the Default Web Site root using ASP.NET configuration inheritance and confused the /blog .NET 2 application.

I didn't want to change the /blog applications' web.config. I just wanted to stop it from inheriting the settings from the parent application. Turns out you can wrap whole sections in a location tag, and then tell that scoped tag to prevent child applications from inheriting.

What you do is change the parent .NET 4 app's web.config to indicate its settings shouldn't flow down to the children, like the .NET 2/3.5 /blog app.

<location path="." inheritInChildApplications="false">
<system.web>
...your system.web stuff goes here
</system.web>
</location>

You can actually read about this in detail in the ASP.NET 4 "breaking changes" documentation. Of course YOU read those closely, don't you? ;)

I chose to change this settings for all of System.Web, but you could do it on a per-section basis if you preferred.

Hope this helps you!

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web

Moving old apps from IIS6 to IIS8 and why Classic Mode exists

March 20, '13 Comments [12] Posted in ASP.NET | IIS
Sponsored By

I had an interesting emailed question today. Here's a paraphrased one sentence version of the question:

Why does an ASP.NET Runtime issue surface in IIS 8.0 Integrated Pool for an application we have run successfully on previous versions of IIS and classic mode in IIS 8.0 ?

It's less interesting that they've moved from IIS6 to IIS8 and more significant that they've moved from Classic Mode to the Integrated Mode pipeline.

So, the short answer. These pipelines are different and if you write an app expecting the behavior of one pipeline and the move it, your observed behavior will likely be different when you move it to the new pipeline.

IIS6 was released in Windows Server 2003 over 10 years ago. Apps used the ISAPI pipeline. By its nature everything in IIS6 is what we think of today as "classic mode," rather than the newer Integrated Pipeline in IIS7.

IIS6 with ASP.NET had two pipelines - the IIS unmanaged one and the managed ASP.NET one. Here's a simplistic but mostly accurate diagram (as is the case with diagrams):

IIS Classic Pipeline is two pipelines. One for IIS and one for ASP.NET

IIS7 and 8 were re-architected with the superior and faster Integrated Mode pipeline but retain "Classic" mode for compatibility.

IIS7 and up is one integrated pipeline

"Doctor, it hurts when I do that."

"Don't do that."

If you're moving an older app from IIS6 (which by definition was only "classic" mode) to IIS 8, the best near-term decision is to run in Classic Mode on IIS8.  Classic Mode is fully supported so you aren’t doing anything wrong by running in classic mode.  It's a mode that is there for a reason.

Think about your reasons and make an educated decision.

Remember that there can be incompatibilities and edge-case behavioral issues when ASP.NET apps attempt to move from the older ISAPI hosting model circa IIS 6 to the newer integrated mode that came in IIS7 without thinking about the ramifications.

I hope this helps.

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Page 1 of 9 in the IIS category Next Page

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