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.
The CZTop::Reactor that manages IO
Return an instance of the handler configured for the handler in the currently-loaded Mongrel2
config that corresponds to appid
.
# File lib/mongrel2/handler.rb, line 115
def self::app_instance_for( appid )
send_spec, recv_spec = self.connection_info_for( appid )
self.log.info " config specs: %s <-> %s" % [ send_spec, recv_spec ]
return new( appid, send_spec, recv_spec )
end
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 124
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 106
def self::run( appid )
app = self.app_instance_for( appid )
self.log.info "Running application %p: %p" % [ appid, app ]
app.run
end
Read a request from the connection and dispatch it.
# File lib/mongrel2/handler.rb, line 270
def accept_request( req )
self.log.info( req.inspect )
res = self.dispatch_request( req )
if res
self.log.info( res.inspect )
@conn.reply( res ) unless @conn.closed?
end
ensure
# Remove any temporarily spooled Mongrel2 files.
begin
if req && req.body && req.body.respond_to?( :path ) && req.body.path
req.body.close unless req.body.closed?
File.unlink( req.body.path )
end
rescue Errno::ENOENT => err
self.log.debug "File already cleaned up: %s (%s)" % [ req.body.path, err.message ]
end
end
Return the Mongrel2::Config::Handlers that corresponds to this app's appid.
# File lib/mongrel2/handler.rb, line 196
def configured_handlers
return Mongrel2::Config::Handler.by_send_ident( self.app_id )
end
Return the Mongrel2::Config::Hosts that have routes that point to this Handler
.
# File lib/mongrel2/handler.rb, line 210
def configured_hosts
routes = self.configured_routes
return Mongrel2::Config::Host.where( id: routes.select(:host_id) )
end
Return the Mongre2::Config::Routes for this Handler
.
# File lib/mongrel2/handler.rb, line 202
def configured_routes
handlers = self.configured_handlers
return Mongrel2::Config::Route.where( target_id: handlers.select(:id) )
end
Return the Mongrel2::Config::Servers that have hosts that have routes that point to this Handler
.
# File lib/mongrel2/handler.rb, line 218
def configured_servers
hosts = self.configured_hosts
return Mongrel2::Config::Server.where( id: hosts.select(:server_id) )
end
Invoke a handler method appropriate for the given request
.
# File lib/mongrel2/handler.rb, line 292
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, and its connection's send_spec and recv_spec.
# File lib/mongrel2/handler.rb, line 186
def handler_config
return self.configured_handlers.where(
send_spec: self.conn.sub_addr,
recv_spec: self.conn.pub_addr
).first
end
Returns a string containing a human-readable representation of the Handler
suitable for debugging.
# File lib/mongrel2/handler.rb, line 326
def inspect
return "#<%p:0x%016x conn: %p>" % [
self.class,
self.object_id * 2,
self.conn,
]
end
Reactor callback – handle an IO event.
# File lib/mongrel2/handler.rb, line 257
def on_socket_event( event )
if event.readable?
req = self.conn.receive
self.accept_request( req )
elsif event.writable?
raise "Request socket became writable?!"
else
raise "Socket event was neither readable nor writable! (%s)" % [ event ]
end
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 234
def restart
raise "can't restart: not running" unless self.reactor
self.log.info "Restarting"
if (( old_conn = @conn ))
self.reactor.unregister( old_conn.request_sock )
@conn = @conn.dup
self.reactor.register( @conn.request_sock, :read, &self.method(:on_socket_event) )
self.log.debug " conn %p -> %p" % [ old_conn, @conn ]
old_conn.close
end
end
Run the handler.
# File lib/mongrel2/handler.rb, line 168
def run
self.log.info "Starting up %p" % [ self ]
self.reactor = CZTop::Reactor.new
self.reactor.register( @conn.request_sock, :read, &self.method(:on_socket_event) )
self.with_signal_handler( self.reactor, *QUEUE_SIGS ) do
self.start_accepting_requests
end
return self # For chaining
ensure
self.log.info "Done: %p" % [ self ]
@conn.close if @conn
end
Shut down the handler.
# File lib/mongrel2/handler.rb, line 225
def shutdown
self.log.info "Shutting down."
self.reactor.stop_polling
@conn.close
end
Start a loop, accepting a request and handling it.
# File lib/mongrel2/handler.rb, line 250
def start_accepting_requests
self.log.info "Starting the request loop."
self.reactor.start_polling( ignore_interrupts: true )
end
Create a new instance of the handler with the specified app_id
, send_spec
, and recv_spec
.
# File lib/mongrel2/handler.rb, line 140
def initialize( app_id, send_spec, recv_spec ) # :notnew:
super() # To the signal handler mixin
@app_id = app_id
@conn = Mongrel2::Connection.new( app_id, send_spec, recv_spec )
@reactor = nil
end
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 346
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 410
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 397
def handle_disconnect( request )
self.log.info "Connection %p closed." % [ request.conn_id ]
return nil
end
Handle a JSON message request
. If not overridden, JSON message ('@route') requests are ignored.
# File lib/mongrel2/handler.rb, line 357
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 373
def handle_websocket( request )
self.log.warn "Unhandled WEBSOCKET frame (%p)" % [ request.headers.path ]
res = request.response
res.make_close_frame( Mongrel2::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 387
def handle_websocket_handshake( handshake )
self.log.warn "Unhandled WEBSOCKET_HANDSHAKE request (%p)" % [ handshake.headers.path ]
self.conn.reply_close( handshake )
return nil
end
Handle an XML message request
. If not overridden, XML message ('<route') requests are ignored.
# File lib/mongrel2/handler.rb, line 365
def handle_xml( request )
self.log.warn "Unhandled XML message request (%p)" % [ request.headers.pack ]
return nil
end
Handle signals.
# File lib/mongrel2/handler.rb, line 437
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
Handle a HUP signal. The default is to restart the handler.
# File lib/mongrel2/handler.rb, line 466
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 458
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 473
def on_user1_signal( signo )
self.log.info "Checkpoint: User signal."
end