class Mongrel2::Handler

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.

# -*- 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.

reactor[RW]

The CZTop::Reactor that manages IO

Public Class Methods

app_instance_for( appid ) click to toggle source

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
connection_info_for( appid ) click to toggle source

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
run( appid ) click to toggle source

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

Public Instance Methods

accept_request( req ) click to toggle source

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
configured_handlers() click to toggle source

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
configured_hosts() click to toggle source

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
configured_routes() click to toggle source

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
configured_servers() click to toggle source

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
dispatch_request( request ) click to toggle source

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::Request
                        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
handler_config() click to toggle source

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
inspect() click to toggle source

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
on_socket_event( event ) click to toggle source

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() click to toggle source

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() click to toggle source

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
shutdown() click to toggle source

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_accepting_requests() click to toggle source

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

Protected Instance Methods

initialize( app_id, send_spec, recv_spec ) click to toggle source

Create a new instance of the handler with the specified app_id, send_spec, and recv_spec.

Calls superclass method
# 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

Handler Methods

↑ top

Public Instance Methods

handle( request ) click to toggle source

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_async_upload_start( request ) click to toggle source

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 = <<~END_MESSAGE % [ request.content_length ]
                If you wish to handle requests like this, either set your server's
                'limits.content_length' setting to a higher value than %d, or override
                #handle_async_upload_start.
                END_MESSAGE

                self.log.warn "Async upload from %s dropped." % [ request.remote_ip ]
                self.log.info( explanation )

                self.conn.reply_close( request )

                return nil
        end
handle_disconnect( request ) click to toggle source

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_json( request ) click to toggle source

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_websocket( request ) click to toggle source

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_websocket_handshake( handshake ) click to toggle source

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_xml( request ) click to toggle source

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

Signal Handling

↑ top

Protected Instance Methods

handle_signal( sig ) click to toggle source

Handle signals.

# File lib/mongrel2/handler.rb, line 438
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
on_hangup_signal( signo ) click to toggle source

Handle a HUP signal. The default is to restart the handler.

# File lib/mongrel2/handler.rb, line 467
def on_hangup_signal( signo )
        self.log.warn "Hangup (%p)" % [ signo ]
        self.restart
end
on_interrupt_signal( signo )
on_termination_signal( signo ) click to toggle source

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 459
def on_termination_signal( signo )
        self.log.warn "Terminated (%p)" % [ signo ]
        self.shutdown
end
Also aliased as: on_interrupt_signal
on_user1_signal( signo ) click to toggle source

Handle a USR1 signal. Writes a message to the log by default.

# File lib/mongrel2/handler.rb, line 474
def on_user1_signal( signo )
        self.log.info "Checkpoint: User signal."
end