Mongrel2 Handler application class. Instances of this class are the applications which connection to one or more Mongrel2 routes and respond to requests.
A dumb, dead-simple example that just returns a plaintext ‘Hello’ document with a timestamp.
#!/usr/bin/env ruby require 'mongrel2/handler' class HelloWorldHandler < Mongrel2::Handler ### The main method to override -- accepts requests and ### returns responses. def handle( request ) response = request.response response.status = 200 response.headers.content_type = 'text/plain' response.puts "Hello, world, it's #{Time.now}!" return response end end # class HelloWorldHandler HelloWorldHandler.run( 'helloworld-handler' )
This assumes the Mongrel2 SQLite config database is in the current directory, and is named ‘config.sqlite’ (the Mongrel2 default), but if it’s somewhere else, you can point the Mongrel2::Config class to it:
require 'mongrel2/config' Mongrel2::Config.configure( :configdb => 'mongrel2.db' )
Mongrel2 also includes support for Configurability, so you can configure it along with your database connection, etc. Just add a 'mongrel2' section to the config with a 'configdb' key that points to where the Mongrel2 SQLite config database lives:
# config.yaml db: uri: postgres://www@localhost/db01 mongrel2: configdb: mongrel2.db whatever_else: ...
Now just loading and installing the config configures Mongrel2 as well:
require 'configurability/config' config = Configurability::Config.load( 'config.yml' ) config.install
If the Mongrel2 config database isn’t accessible, or you need to configure the Handler’s two 0mq connections yourself for some reason, you can do that, too:
app = HelloWorldHandler.new( 'helloworld-handler',
'tcp://otherhost:9999', 'tcp://otherhost:9998' )
app.run
Signals we handle
The app ID the app was created with
The handler’s Mongrel2::Connection object.
Return the send_spec and recv_spec for the given appid from
the current configuration database. Returns nil if no Handler is configured with appid as
its sender_id.
# File lib/mongrel2/handler.rb, line 112
def self::connection_info_for( appid )
self.log.debug "Looking up handler spec for appid %p" % [ appid ]
hconfig = Mongrel2::Config::Handler.by_send_ident( appid ).first or
raise ArgumentError, "no handler with a send_ident of %p configured" % [ appid ]
self.log.debug " found: %s" % [ hconfig.values ]
return hconfig.send_spec, hconfig.recv_spec
end
Create an instance of the handler using the config from the database with
the given appid and run it.
# File lib/mongrel2/handler.rb, line 102
def self::run( appid )
self.log.info "Running application %p" % [ appid ]
send_spec, recv_spec = self.connection_info_for( appid )
self.log.info " config specs: %s <-> %s" % [ send_spec, recv_spec ]
new( appid, send_spec, recv_spec ).run
end
Read a request from the connection and dispatch it.
# File lib/mongrel2/handler.rb, line 207
def accept_request
req = @conn.receive
self.log.info( req.inspect )
res = self.dispatch_request( req )
if res
self.log.info( res.inspect )
@conn.reply( res ) unless @conn.closed?
end
end
Invoke a handler method appropriate for the given request.
# File lib/mongrel2/handler.rb, line 220
def dispatch_request( request )
if request.is_disconnect?
self.log.debug "disconnect!"
self.handle_disconnect( request )
return nil
elsif request.upload_started?
self.log.debug "async upload start!"
return self.handle_async_upload_start( request )
else
self.log.debug "%s request." % [ request.headers['METHOD'] ]
case request
when Mongrel2::WebSocket::ClientHandshake
return self.handle_websocket_handshake( request )
when Mongrel2::WebSocket::Frame
return self.handle_websocket( request )
when Mongrel2::HTTPRequest
return self.handle( request )
when Mongrel2::JSONRequest
return self.handle_json( request )
when Mongrel2::XMLRequest
return self.handle_xml( request )
else
self.log.error "Unhandled request type %s (%p)" %
[ request.headers['METHOD'], request.class ]
return nil
end
end
end
Return the Mongrel2::Config::Handler that corresponds to this app’s appid.
# File lib/mongrel2/handler.rb, line 162
def handler_config
return Mongrel2::Config::Handler.by_send_ident( self.app_id ).first
end
Returns a string containing a human-readable representation of the Handler suitable for debugging.
# File lib/mongrel2/handler.rb, line 254
def inspect
return "#<%p:0x%016x conn: %p>" % [
self.class,
self.object_id * 2,
self.conn,
]
end
Restart the handler. You should override this if you want to re-establish database connections, flush caches, or other restart-ey stuff.
# File lib/mongrel2/handler.rb, line 178
def restart
self.log.info "Restarting"
old_conn = @conn
@conn = @conn.dup
self.log.debug " conn %p -> %p" % [ old_conn, @conn ]
old_conn.close
end
Run the handler.
# File lib/mongrel2/handler.rb, line 148
def run
self.log.info "Starting up %p" % [ self ]
self.set_signal_handlers
self.start_accepting_requests
return self # For chaining
ensure
self.restore_signal_handlers
self.log.info "Done: %p" % [ self ]
end
Shut down the handler.
# File lib/mongrel2/handler.rb, line 168
def shutdown
self.log.info "Shutting down."
self.ignore_signals
@conn.close
Mongrel2.zmq_context.close
end
Start a loop, accepting a request and handling it.
# File lib/mongrel2/handler.rb, line 188
def start_accepting_requests
until @conn.closed?
ZMQ.select([ @conn.request_sock ])
self.accept_request unless @conn.closed?
end
rescue ZMQ::Error => err
self.process_signal_queue
unless @conn.closed?
self.log.error "%p while accepting requests: %s" % [ err.class, err.message ]
self.log.debug { err.backtrace.join("\n ") }
# Restart the method
retry
end
end
Create a new instance of the handler with the specified
app_id, send_spec, and recv_spec.
# File lib/mongrel2/handler.rb, line 128
def initialize( app_id, send_spec, recv_spec ) # :notnew:
@app_id = app_id
@conn = Mongrel2::Connection.new( app_id, send_spec, recv_spec )
Thread.main[:signal_queue] = []
end
These methods are the principle mechanism for defining the functionality of your handler. The logic that dispatches to these methods is all contained in the dispatch_request method, so if you want to do something completely different, you should override that instead.
The main handler function: handle the specified HTTP request
(a Mongrel2::Request) and return a response (Mongrel2::Response). If not overridden, this
method returns a ‘204 No Content’ response.
# File lib/mongrel2/handler.rb, line 274
def handle( request )
self.log.warn "No default handler; responding with '204 No Content'"
response = request.response
response.status = HTTP::NO_CONTENT
return response
end
Handle an asynchronous upload start notification. These are sent to notify
the handler that a request that exceeds the server’s
limits.content_length has been received. The default
implementation cancels any such uploads by replying with an empty string.
If the request should be accepted, your handler should override this and do
nothing if the request should continue. You’ll receive a new request via
the regular callback when the upload completes whose entity body is open to
the spooled file.
# File lib/mongrel2/handler.rb, line 338
def handle_async_upload_start( request )
explanation = "If you wish to handle requests like this, either set your server's "
explanation << "'limits.content_length' setting to a higher value than %d, or override " %
[ request.content_length ]
explanation << "#handle_async_upload_start."
self.log.warn "Async upload from %s dropped." % [ request.remote_ip ]
self.log.info( explanation )
self.conn.reply_close( request )
return nil
end
Handle a disconnect notice from Mongrel2 via
the given request. Its return value is ignored.
# File lib/mongrel2/handler.rb, line 325
def handle_disconnect( request )
self.log.info "Unhandled disconnect notice."
return nil
end
Handle a JSON message request. If not overridden, JSON message
(‘@route’) requests are ignored.
# File lib/mongrel2/handler.rb, line 285
def handle_json( request )
self.log.warn "Unhandled JSON message request (%p)" % [ request.headers.path ]
return nil
end
Handle a WebSocket frame in
request. If not overridden, WebSocket connections are closed with a policy
error status.
# File lib/mongrel2/handler.rb, line 301
def handle_websocket( request )
self.log.warn "Unhandled WEBSOCKET frame (%p)" % [ request.headers.path ]
res = request.response
res.make_close_frame( WebSocket::CLOSE_POLICY_VIOLATION )
self.conn.reply( res)
self.conn.reply_close( request )
return nil
end
Handle a WebSocket handshake HTTP
request. If not overridden, this method drops the connection.
# File lib/mongrel2/handler.rb, line 315
def handle_websocket_handshake( handshake )
self.log.warn "Unhandled WEBSOCKET_HANDSHAKE request (%p)" % [ request.headers.path ]
self.conn.reply_close( request )
return nil
end
Handle an XML message request. If not overridden, XML message
(‘<route’) requests are ignored.
# File lib/mongrel2/handler.rb, line 293
def handle_xml( request )
self.log.warn "Unhandled XML message request (%p)" % [ request.headers.pack ]
return nil
end
These methods set up some behavior for starting, restarting, and stopping your application when a signal is received. If you don’t want signals to be handled, override set_signal_handlers with an empty method.
Handle signals.
# File lib/mongrel2/handler.rb, line 400
def handle_signal( sig )
self.log.debug "Handling signal %s" % [ sig ]
case sig
when :INT, :TERM
self.on_termination_signal( sig )
when :HUP
self.on_hangup_signal( sig )
when :USR1
self.on_user1_signal( sig )
else
self.log.warn "Unhandled signal %s" % [ sig ]
end
end
Set all signal handlers to ignore.
# File lib/mongrel2/handler.rb, line 374
def ignore_signals
self.log.debug "Ignoring signals."
QUEUE_SIGS.each do |sig|
Signal.trap( sig, :IGNORE )
end
end
Handle a HUP signal. The default is to restart the handler.
# File lib/mongrel2/handler.rb, line 429
def on_hangup_signal( signo )
self.log.warn "Hangup (%p)" % [ signo ]
self.restart
end
Handle a TERM signal. Shuts the handler down after handling any current request/s. Also aliased to on_interrupt_signal.
# File lib/mongrel2/handler.rb, line 421
def on_termination_signal( signo )
self.log.warn "Terminated (%p)" % [ signo ]
self.shutdown
end
Handle a USR1 signal. Writes a message to the log by default.
# File lib/mongrel2/handler.rb, line 436
def on_user1_signal( signo )
self.log.info "Checkpoint: User signal."
end
Handle any queued signals.
# File lib/mongrel2/handler.rb, line 391
def process_signal_queue
# Look for any signals that arrived and handle them
while sig = Thread.main[:signal_queue].shift
self.handle_signal( sig )
end
end
Set the signal handlers back to their defaults.
# File lib/mongrel2/handler.rb, line 383
def restore_signal_handlers
self.log.debug "Restoring default signal handlers."
QUEUE_SIGS.each do |sig|
Signal.trap( sig, :DEFAULT )
end
end
Set up signal handlers for common signals that will shut down, restart, etc.
# File lib/mongrel2/handler.rb, line 365
def set_signal_handlers
self.log.debug "Setting up deferred signal handlers."
QUEUE_SIGS.each do |sig|
Signal.trap( sig ) { Thread.main[:signal_queue] << sig }
end
end