Because Mongrel2 makes it easy to have discrete handler processes managing different URI routes, you'll quickly find benefit from refactoring common code into reusable components.
As mentioned in the Strelka Tutorial section, Strelka breaks out functionality into a set of core plugins that allow you to cherry pick the capabilities you need. It's easy to create your own plugins for optional loading of shared behavior into any number of handlers.
A Strelka plugin is just a module under the Strelka::App namespace that is extended by the
Strelka::Plugin class. Once extended, the
plugin participates in the
lifecycle, and is able to alter it via hooks. The plugin only participates
for handlers that load it via the
Lets start by creating and naming an empty plugin. We'll call it
require 'strelka' require 'strelka/app' module Strelka::App::DBLogger extend Strelka::Plugin end
It's important to save the plugin under a path that Strelka can locate it. It can go anywhere in your
$LOAD_PATH, but should be under a
subdirectory, and the filename should match the class.
We'll save this to
lib/strelka/app/dblogger.rb, and Strelka applications can use it like so:
require 'strelka' class ExampleApplication < Strelka::App plugins :routing, :dblogger get do |req| res = req.response res.content_type = 'text/plain' return res.body << "Hi! I'll be logged!" end end ExampleApplication.run
The request is passed through plugins sequentually. You can control where
in the chain your plugin belongs, by using the
run_inside methods. Both methods accept a comma separated
list of other plugin names.
In this example case, we want the logger to log the request before the other core plugins run, so any errors still make it out to the log.
require 'strelka' require 'strelka/app' module Strelka::App::DBLogger extend Strelka::Plugin run_outside :auth, :filters, :negotiation, :parameters, :routing, :sessions, :templating end
There are three primary extension points you can override in your plugin.
All hooks absolutely require you to
super at some point, so
the request/response chain passes through your plugin.
Make any changes to the
request that are necessary before
handling it and return it. This is an alternate extension-point for plugins
that wish to modify or replace the request before the request cycle is
request and return a
response. This is
the main extension-point for the plugin system. Without being overridden or
extended by plugins, this method just returns the default Mongrel2
Make any changes to the
response that are necessary before
handing it to Mongrel and return it. This is an alternate extension- point
for plugins that wish to modify or replace the response after the whole
request cycle is completed.
For our logging purposes, we want to hook the
method. We won't be altering the response itself, but just reading
attributes from it and squirreling them away. Most notably, the
response has access to the
response object, and
visa versa. You can find more detail for these hooks in the API
documentation for Strelka::App.
Here's the complete plugin.
require 'strelka' require 'strelka/app' require 'sequel' module Strelka::App::DBLogger extend Strelka::Plugin run_outside :auth, :filters, :negotiation, :parameters, :routing, :sessions, :templating def initialize( * ) super @db = Sequel.sqlite( '////tmp/strelka_access.db' ) @db.create_table( :log ) do timestamptz :date, :null => false varchar :agent, :size => 255 varchar :remote_ip, :null => false smallint :status varchar :method, :size => 8, :null => false varchar :path, :size => 255 varchar :query, :size => 255 varchar :referer, :size => 255 end unless @db.table_exists?( :log ) end attr_reader :db def fixup_response( response ) request = response.request self.log.debug self.db[ :log ].insert( :date => Time.now.to_s, :agent => request.headers.user_agent, :remote_ip => request.remote_ip.to_s, :status => response.status, :method => request.verb.to_s, :path => request.uri.path, :query => request.uri.query, :referer => request.headers.referer ) super end end
Handler startup creates the database and the logging schema, and every
request performs an
insert with the data we're after.
There's plenty of room for improvement here (configurable db location,
prepared statements), but hopefully that gives you a first-round idea of
how easy it is to add pluggable functionality to Strelka.