Solving the same problem in C#, Ruby and Clojure

16 November 2011 06:12

I was interested to read Paul Ingles article on uSwitch’s handling of SSL.  Particularly interested because I remember writing the code he is replacing (It was about seven years ago, I believe...).  I thought makes a good case study in how your language choice affects your code.  To recap his article, uSwitch’s architecture offloaded SSL to hardware.  This meant that detecting whether you were running under SSL was impossible without a custom header.  Furthermore, the SSL/Non-SSL functionality was a bit more complex than a simple “redirect insecure pages”.

However, after you’ve added the custom header to your load balances from a programming perspective your problems are only just starting.  The uSwitch code base at the time was ASP.NET 2.0.  To access the request, you had to use the Request object, which was sealed.  So you didn’t really have any other option than to define a method that tested for SSL and shout at anyone who forgot to use it.  uSwitch had a large number of developers and high turnover, which made things like this an on-going issue.

Solving in Ruby

Paul’s solution to this problem is pretty much the right thing to do in Ruby.  He modifies the behaviour of Rack in order to support his use case.  No-one else working on an unrelated part of the code base ever needs to know about this subtlety of the SSL implementation.  Thus, a dynamic typed language with mutable classes demonstrates massively better separation than the statically-typed C#.

So Ruby has taken the pain away in a page of code.  However, can we improve on this if we move to a dynamically typed language with immutable state?  Like my favourite language, Clojure.  Well, are there any problems left?  Yes, although admittedly they’re not huge:

  • We’ve moved the problem of “make people call this custom function” to “make people call this standard function”.  If they’re used to old versions of the rack and examine env directly, they’ll circumvent it.  (Admittedly, it’s my belief that Forward’s developers are typically of higher quality than old-school uSwitch’s.)
  • We’ve modified Rack to get work done, which is fine as long as no-one else does the same thing.
  • We’ve actually had to duplicate some of the functionality from Rack to make sure it behaves the same way.

Solving in Clojure

Now, the only way to remove the first problem is to actually modify the request.  In languages with mutable state this is usually regarded as a bad thing, because it’s very hard to track exactly what could be affected by your change.  Immutability solves that.  Modifying the request also prevents us from having to modify the underlying architecture, so any solution we build will be fully composable with anything else we choose to use.

(defn non-standard-https [handler request]  
    (handler (if (get-in request :headers "HTTP_USWITCH_HTTPS")
                 (assoc request :scheme :https)
                 request)))
                
(def handled-routes (partial non-standard-https routes))

What has the Clojure version given us?  Because the request is immutable, only the routes we choose to affect take the functionality.  (Not really an issue in this particular case, but still a nice property to have.)  We've not had to modify Ring: its middleware is fully composable and because of this property, we haven't had to duplicate Ring functionality in our own code.  It’s not configurable, but at four lines, it hardly needs to be.

To recap:

  • Handling this simple piece of functionality at uSwitch in C# was a constant code-quality issue.
  • Handling it in Ruby pretty much fixes the problem.
  • Handling it in Clojure makes it look like a non-problem.

I’m genuinely of the belief that the only thing Clojure’s web solution lacks is maturity.

Technorati Tags: ,,,
Comments
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Roger Lipscombe on 16/11/2011 07:06
Be careful not to confuse language with framework or platform.
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Paul Ingles on 16/11/2011 08:33
(also posted as a comment on my original post).

Thanks for the follow-up, was pretty sure this had 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.
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Josh on 16/11/2011 14:11
Woah, is this whole site REALLY all underlined? Fix that, sheesh!
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Julian on 16/11/2011 22:28
@Roger True enough, but in this case the languages define a philosophy that runs right through the platform. You could rewrite the request in ASP.NET MVC in the same way as in Clojure, but it would take over a page of code, and it's unlikely anyone would. Equally, monkey-patching is prevalent in Ruby, and it's hard to get Clojure people to shut up about composability.

@Paul I'm pretty sure I wouldn't have changed the header either. Way too many unexpected things could go wrong.

I've got to admit, I think Ruby's got a great web solution. For me, it feels like there's a huge volume of stuff to learn to be productive, though. I'm self-taught on most of this stuff, and I found Clojure much easier to learn on your own.

@Josh Sorry, Windows Live Writer+Outlook can do really horrible things to a post. The offender this time was a stray "< u />" tag. I'm not sure what HTML standard Microsoft were reading...
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Max on 18/11/2011 10:32
A bit confused - what would the c# solution look like - a 2 line extension method .IsSsl() on HttpRequestBase, or have I missed the point?
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Max on 18/11/2011 10:58
Oh right - you want ASP to think the scheme is https
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Julian on 22/11/2011 07:06
@Max The challenge was to come up with a way of doing it that reduced the likelihood of errors in the codebase. The .NET 3.5 style IsSsl method is certainly better than a .NET 2.0 static method like we had in the original code base. However, it's still undiscoverable: if you don't have the namespace loaded you might not know it was there.

Paul's solution replaces an existing function with a better one in the context in which he's working, but still suffers from the problem of dealing with developers who aren't aware of the newer standard API functions.

Ironically, you could fix it in ASP.NET MVC by creating a decorator around HttpRequestBase. This wasn't even possible in old-style WebForms code. Sadly, it's still an insane amount of work and it's unlikely anyone would regard it as a good idea.
Gravatar
# re: Solving the same problem in C#, Ruby and Clojure
Posted by Paul Ingles on 22/11/2011 21:37
To be sure, the extension maintains the behaviour of a standard API function (it's part of Rack's Request object) but allows developers to specify additional ways of matching to the https scheme. In our case the headers added by the load-balancer were non-standard and thus Rack doesn't detect by default.

I still implemented it untidily though ;)
Something to add?

Talking sense? Talking rubbish? Something I'm missing? Let me know!

Fields denoted with a "*" are required.

 (will not be displayed)

 
Please add 6 and 2 and type the answer here:

Preview Your Comment