Recognising SSL Requests in Rack with Custom Headers

For a while now I’ve been working on replacing bits of’s .NET heritage with some new Clojure and Ruby blood. A problem with one of the new applications came up when we needed it to recognise (and behave differently) when an SSL secured request was made because of non-standard headers (although I’m yet to successfully Google the RFC where HTTP_X_FORWARDED_PROTO is defined :).

Polyglot Deployment

New applications are deployed on different machines but proxied by the old application servers- letting us mount new applications inside the current website. Although the servers run IIS and ASP.NET they’re configured much like nginx’s proxy_pass module (for example). You set the matching URL and the URL you’d like to proxy to. also use hardware load balancers in front of the application and web servers.

Rack Scheme Detection

Recent releases of Rack include a ssl? method on the Request that return true if the request is secure. This, internally, calls scheme to check whether any of the HTTP headers that specify an HTTPS scheme are set (the snippet from Rack’s current code is below).

Unfortunately, this didn’t recognise our requests as being secure. The hardware load-balancer was setting a custom header that wasn’t anything Rack would expect. The quickest (and perhaps dirty?) solution- a gem, rack-scheme-detect, that allows additional mappings to be provided- the rest of Rack will continue to work (and all of the other middleware and apps above it).

The following snippet shows how it can be used inside a classic Sinatra application to configure the additional mapping.

Of course we’ll be making sure we also set one of the standard headers. But, for anyone who needs a quick solution when that’s not immediately possible, hopefully rack-scheme-detect might save some time.

4 responses
Glad to see that codebase is getting some attention. :) You might be interested in my followup:>In short, I can understand why you're ditching the C#, but no so sure why you're using Ruby. (Although I'm guessing familiarity has something to do with it: that's why uSwitch was coded in C#, after all.)
Thanks for the follow-up, was pretty sure this was dropped into the ether :)

As you say, the way this is currently implemented isn't really ideal (and almost certainly a bad thing): method chaining/decoration with modules would probably be preferable as it would leave the existing behaviour well alone and not duplicate it. At the time I was having problems making that work so simplified and left it as-is (we needed a solution quickly).

The best thing probably would have been to change the custom header being added at the load-balancer to one that other equivalent systems use (HTTP_X_FORWARDED_SSL for example), but that's a scary wide-ranging thing to change so we decided to go for the more focused fix as we're migrating anyway.

As for Ruby/Clojure, I think you're right- maturity is definitely an issue. We've built the odd web app in Clojure but these tend to be behind-the-scenes administrative stuff (aside from our core pricing/comparison service). For us, Ruby currently provides a better whole for building user-facing web apps.

A little late here, but came across this while researching custom headers with Rack.

Any reason you didn't just attempt to access Rack::Request.env directly? From my tests, it looks like they get passed through. Ex:

curl -H 'Authorization: fl9f0fjp0q93t1039' -H 'Foo: bar' http://localhost:3001

Both request.env["HTTP_FOO"] and request.env["HTT{_AUTHORIZATION"] are available in my Padrino test (based on sinatra).

Not trying to be critical here, just wondering if I'm missing something.

You are correct- you can access the headers directly. But, the benefit of Rack is that it provides an abstraction layer for applications- if you augment Rack with code to help it understand how to identify SSL requests (as we needed to at the time), any application framework on top (such as Rails or Sinatra) would work without needing to make any application changes.

The Rack middleware lets us plug it into the application stack without needing to change much about the applications themselves.