Hacking Dispatcher without DI

For reasons I won't get into, it became necessary to make Rails go faster. But only for a couple url's, and without need for the majority of the framework. For a while, we ran these scripts with raw FCGI and a simplified Dispatcher that let you work with the request and response directly, bypassing the main Rails workflow.

But now we have the wonderful RailsFCGIHandler, with graceful signal handling, app reloading and more. I wanted our custom dispatcher to benefit from that too.

So, we want to use the RailsFCGIHandler but handle requests with our stripped down Dispatcher before Rails gets to it. My first thought was to override FCGIHandler#process_request

class CustomRailsFCGIHandler
  def process_request(cgi)
    MyDispatcher.dispatch(cgi)
    # Dispatcher.dispatch(cgi)
  rescue Object => e
    raise if SignalException === e
    dispatcher_error(e)
  end
end

CustomRailsFCGIHandler.process!

That's ok, but we've duplicated some tricky exception handling logic. Even worse, it's treating Ruby like a static language. We can do much better. First, there's no need to subclass RailsFCGIHandler. Simply opening the class and redefining process_request would do the same thing.

But, here's the cool part. We don't have to modify RailsFCGIHandler at all! The only functionality we need to change is in Dispatcher, so let's just modify it.

class Dispatcher
  class <<self
    alias :old_dispatch :dispatch
  end
  def self.dispatch(cgi)
    SimpleDispatcher.dispatch(cgi,false) do |request, response|
      case request.path
        when "/one" then Tracker::Controller.process(request, response, :one)
        when "/two" then Tracker::Controller.process(request, response, :two)
        else old_dispatch(cgi)
      end
    end
  end
end

RailsFCGIHandler.process!

That's it. Now our two high-availability url's are handled while still supporting Rails' normal workflow.

Here's SimpleDispatcher for reference. All it does is instantiate Rails' request and response objects, yield them, then take care of session propagation and write a response.

class SimpleDispatcher
  def self.dispatch(cgi = CGI.new, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS)
    request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
    begin
      yield request, response
    rescue
      response.body << $!.to_s
    ensure
      request.session.update if request.session && !request.session.kind_of?(Hash)
      response.out unless response.body.empty?
    end
  end
end

Back to Dependency Injection. In a static language, hardcoding Dispatcher.dispatch(cgi) would have been a sin. It forever ties us to a specific implementation. Design patterns help - template method or factory method come to mind. Or using a DI container with setter injection. But each of these techniques causes excess overhead in both usage and design.

The Ruby way gives us a simple (almost naïve) implementation that's as flexible as the most complicated architectural patterns.

This point is worth repeating. Without any coding or design overhead, the result is highly flexible. Sure you can accomplish the same result in Java, et al but how many more lines of code+config+tests did it take?

There is 1 comment

  1. 4 days later, Jim Ryan said...

    Nice little tutorial. Thanks