Mongrel2::

Handler

class
Superclass
Object
Included Modules
Mongrel2::Constants

Mongrel2 Handler application class. Instances of this class are the applications which connection to one or more Mongrel2 routes and respond to requests.

Example

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

Constants

QUEUE_SIGS

Signals we handle

Attributes

app_id[R]

The app ID the app was created with

conn[R]

The handler’s Mongrel2::Connection object.

Public Class Methods

anchor
connection_info_for( appid )

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
anchor
run( appid )

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

Public Instance Methods

anchor
accept_request()

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
anchor
dispatch_request( request )

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
anchor
handler_config()

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
anchor
inspect()

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
anchor
restart()

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
anchor
run()

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
anchor
shutdown()

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
anchor
start_accepting_requests()

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

Protected Instance Methods

anchor
initialize( app_id, send_spec, recv_spec )

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

Handler Methods

↑ top

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.

Public Instance Methods

anchor
handle( request )

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
anchor
handle_async_upload_start( request )

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
anchor
handle_disconnect( request )

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
anchor
handle_json( request )

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
anchor
handle_websocket( request )

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
anchor
handle_websocket_handshake( handshake )

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
anchor
handle_xml( request )

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

Signal Handling

↑ top

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.

Protected Instance Methods

anchor
handle_signal( sig )

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
anchor
ignore_signals()

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
anchor
on_hangup_signal( signo )

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
anchor
on_interrupt_signal( signo )
anchor
on_termination_signal( signo )

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
Also aliased as: on_interrupt_signal
anchor
on_user1_signal( signo )

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
anchor
process_signal_queue()

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
anchor
restore_signal_handlers()

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
anchor
set_signal_handlers()

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