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
Nice little tutorial. Thanks