The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Mongrel, SSL, and Apache 1.3

3 October 2006 — 2-minute read

So, you’ve heard all about this mongrel thing that has Rails programmers everywhere talking. You’re anxious to give the whole setup a try, but…

You’re stuck on Apache 1.3, and you’ve got certain actions in your app that require SSL support.

This happened to me recently. Parts of my app require SSL, and other parts do not. The general solution, if you are using Apache 2, is to set the X-FORWARDED-PROTO request header to “https” before proxying the (decrypted) request to mongrel. Unfortunately, Apache 1.3 has no mechanism for setting request headers!

Here’s how I worked around it. I changed my vhost setting thus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<VirtualHost a.b.c.d>
  ServerName       foo.bar.com

  ProxyPass        /  http://localhost:3000/
  ProxyPassReverse /  http://localhost:3000/
</VirtualHost>

<VirtualHost a.b.c.d:443>
  ServerName       foo.bar.com

  ProxyPass        /  http://localhost:3000/ssl/
  ProxyPassReverse /  http://localhost:3000/ssl/

  ...
</VirtualHost>

In the first block, I define the proxy to just forward the request to port 3000, unaltered. (I’ve got a pen process running on port 3000, which then balances requests across multiple mongrel instances.) In the second block, however, I configure the proxy to forward to the same port, but I append the path /ssl/ to it. This, then, gives me a way to differentiate SSL requests from non-SSL requests in my app.

I then make two tweaks to my app. For the first tweak, I add a new route:

1
2
3
4
ActionController::Routing::Routes.draw do |map|
  # ...
  map.connect "ssl/*suburi", :controller => "ssl_proxy", :action => "reprocess"
end

Then, I add a new controller, which will process (and forward) SSL requests:

1
2
3
4
5
6
7
8
9
10
11
class SslProxyController < ActionController::Base
  def reprocess
    request.env["REQUEST_URI"] = "/#{params[:suburi]}"
    request.env["HTTPS"] = "on"

    controller = ActionController::Routing::Routes.recognize(request)

    controller.process(request, response)
    @performed_render = true
  end
end

This just sets the REQUEST_URI and HTTPS environment variables appropriately, requests routing to recognize the new path, and has the resulting controller process the request. I then tell Rails that a render was performed, just to prevent it from trying to render the non-existant reprocess.rhtml template.

Now, this may or may not work for every situation, but it works well enough for what I need. Eventually I’ll be moving to Apache 2 and can throw this stop-gap code away, but it at least lets me get up and running with mongrel sooner, rather than later.

Reader Comments

Just out of curiousity... why are you stuck with 1.3?
Mostly because the server in question is not part of a redundant set of web servers, and we can't afford to take it down to move everything to apache 2 just yet. We'll get some redundancy in there soon, and then we'll move to apache 2.
Any reason why you're not using 'render :nothing => true' to stop rendering? Seems a bit more like normal renders that way... although I suspect that will do just what you did with @performed_render ;-)
If I were to use @render :nothing => true@, it would clobber anything that was rendered by the other controller. The @SslProxyController@ has no knowledge of what occurs in the new controller, and vice versa. The best I can do is "trick" the @SslProxyController@ into thinking something has been rendered, and I do that by setting that instance variable.