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)
(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.
- 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.