Rails 2.2.2 memoization gotchas

For various reasons, we are still running on Apache 1.3 which is fine, except for the fact that there is no easy way of determining whether a request is via an SSL session or not.

Unfortunately, this process was broken by the upgrade to Rails 2.2.2.

No probs. We just rewrite the URL to add an “ssl” prefix before it gets passed to the Rails engine.

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ http://127.0.0.1:8000/ssl/$1 [P,L]
ProxyPassReverse / http://127.0.0.1:8000/ssl/

From there, a simple route will pass the request on to our SSL Proxy controller.

map.connect 'ssl/*suburi',
            :controller => 'ssl_proxy',
            :action => 'reprocess'

Now all our ‘reprocess’ method has to do is take the request, rewrite the URI to its original form, and set an environment variable to let us know it’s an SSL request.

class SslProxyController < ActionController::Base
  def reprocess
    request.env["REQUEST_URI"] = "/#{params[:suburi].join('/')}"
    request.env["HTTPS"] = "on"

    controller = ActionController::Routing::Routes.recognize(request)
    controller.process(request, response)
    @performed_render = true
  end
end

This all broke down with the upgrade to Rails 2.2.2.

One of the new features of 2.2.2 is the addition of memoization to various system classes, including the ‘request’ object. This means that ‘request_uri’ is now memoized, and that even if we update the environment variable ‘REQUEST_URI’, the memoized value doesn’t change. When the route is recognized, it resolves to ‘reprocess’ again, and we end up in an endless loop.

The answer turns to reasonably easy to implement. All we need to do is add the following line at the start of the ‘reprocess’ method.

request.unmemoize_all if request.respond_to? :unmemoize_all

This code simply clears all the memoized variables, so they can all be loaded again using our new, proper URI.