Class: Mongrel2::Handler
- Inherits:
-
Object
- Object
- Mongrel2::Handler
- Extended by:
- Loggability
- Includes:
- CZTop::Reactor::SignalHandling, Constants
- Defined in:
- lib/mongrel2/handler.rb
Overview
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
Constant Summary collapse
- QUEUE_SIGS =
Signals we handle
[ :INT, :TERM, :HUP, :USR1, # :TODO: :QUIT, :WINCH, :USR2, :TTIN, :TTOU ] & Signal.list.keys.map( &:to_sym )
Constants included from Constants
Constants::DATA_DIR, Constants::DEFAULT_CONFIG_SCRIPT, Constants::DEFAULT_CONFIG_URI, Constants::DEFAULT_CONTROL_SOCKET, Constants::MAX_BROADCAST_IDENTS
Instance Attribute Summary collapse
-
#app_id ⇒ Object
readonly
The app ID the app was created with.
-
#conn ⇒ Object
readonly
The handler’s Mongrel2::Connection object.
-
#reactor ⇒ Object
The CZTop::Reactor that manages IO.
Class Method Summary collapse
-
.app_instance_for(appid) ⇒ Object
Return an instance of the handler configured for the handler in the currently-loaded Mongrel2 config that corresponds to
appid
. -
.connection_info_for(appid) ⇒ Object
Return the send_spec and recv_spec for the given
appid
from the current configuration database. -
.run(appid) ⇒ Object
Create an instance of the handler using the config from the database with the given
appid
and run it.
Instance Method Summary collapse
-
#accept_request(req) ⇒ Object
Read a request from the connection and dispatch it.
-
#configured_handlers ⇒ Object
Return the Mongrel2::Config::Handlers that corresponds to this app’s appid.
-
#configured_hosts ⇒ Object
Return the Mongrel2::Config::Hosts that have routes that point to this Handler.
-
#configured_routes ⇒ Object
Return the Mongre2::Config::Routes for this Handler.
-
#configured_servers ⇒ Object
Return the Mongrel2::Config::Servers that have hosts that have routes that point to this Handler.
-
#dispatch_request(request) ⇒ Object
Invoke a handler method appropriate for the given
request
. -
#handle(request) ⇒ Object
The main handler function: handle the specified HTTP
request
(a Mongrel2::Request) and return a response (Mongrel2::Response). -
#handle_async_upload_start(request) ⇒ Object
Handle an asynchronous upload start notification.
-
#handle_disconnect(request) ⇒ Object
Handle a disconnect notice from Mongrel2 via the given
request
. -
#handle_json(request) ⇒ Object
Handle a JSON message
request
. -
#handle_websocket(request) ⇒ Object
Handle a WebSocket frame in
request
. -
#handle_websocket_handshake(handshake) ⇒ Object
Handle a WebSocket handshake HTTP
request
. -
#handle_xml(request) ⇒ Object
Handle an XML message
request
. -
#handler_config ⇒ Object
Return the Mongrel2::Config::Handler that corresponds to this app’s appid, and its connection’s send_spec and recv_spec.
-
#initialize(app_id, send_spec, recv_spec) ⇒ Handler
constructor
Create a new instance of the handler with the specified
app_id
,send_spec
, andrecv_spec
. -
#inspect ⇒ Object
Returns a string containing a human-readable representation of the Handler suitable for debugging.
-
#on_socket_event(event) ⇒ Object
Reactor callback – handle an IO event.
-
#restart ⇒ Object
Restart the handler.
-
#run ⇒ Object
Run the handler.
-
#shutdown ⇒ Object
Shut down the handler.
-
#start_accepting_requests ⇒ Object
Start a loop, accepting a request and handling it.
Constructor Details
#initialize(app_id, send_spec, recv_spec) ⇒ Handler
Create a new instance of the handler with the specified app_id
, send_spec
, and recv_spec
.
141 142 143 144 145 146 147 148 |
# File 'lib/mongrel2/handler.rb', line 141 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 |
Instance Attribute Details
#app_id ⇒ Object (readonly)
The app ID the app was created with
161 162 163 |
# File 'lib/mongrel2/handler.rb', line 161 def app_id @app_id end |
#conn ⇒ Object (readonly)
The handler’s Mongrel2::Connection object.
157 158 159 |
# File 'lib/mongrel2/handler.rb', line 157 def conn @conn end |
#reactor ⇒ Object
The CZTop::Reactor that manages IO
165 166 167 |
# File 'lib/mongrel2/handler.rb', line 165 def reactor @reactor end |
Class Method Details
.app_instance_for(appid) ⇒ Object
Return an instance of the handler configured for the handler in the currently-loaded Mongrel2 config that corresponds to appid
.
116 117 118 119 120 |
# File 'lib/mongrel2/handler.rb', line 116 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) ⇒ 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
.
125 126 127 128 129 130 131 132 |
# File 'lib/mongrel2/handler.rb', line 125 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) ⇒ Object
Create an instance of the handler using the config from the database with the given appid
and run it.
107 108 109 110 111 |
# File 'lib/mongrel2/handler.rb', line 107 def self::run( appid ) app = self.app_instance_for( appid ) self.log.info "Running application %p: %p" % [ appid, app ] app.run end |
Instance Method Details
#accept_request(req) ⇒ Object
Read a request from the connection and dispatch it.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/mongrel2/handler.rb', line 271 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. ] end end |
#configured_handlers ⇒ Object
Return the Mongrel2::Config::Handlers that corresponds to this app’s appid.
197 198 199 |
# File 'lib/mongrel2/handler.rb', line 197 def configured_handlers return Mongrel2::Config::Handler.by_send_ident( self.app_id ) end |
#configured_hosts ⇒ Object
Return the Mongrel2::Config::Hosts that have routes that point to this Handler.
211 212 213 214 |
# File 'lib/mongrel2/handler.rb', line 211 def configured_hosts routes = self.configured_routes return Mongrel2::Config::Host.where( id: routes.select(:host_id) ) end |
#configured_routes ⇒ Object
Return the Mongre2::Config::Routes for this Handler.
203 204 205 206 |
# File 'lib/mongrel2/handler.rb', line 203 def configured_routes handlers = self.configured_handlers return Mongrel2::Config::Route.where( target_id: handlers.select(:id) ) end |
#configured_servers ⇒ Object
Return the Mongrel2::Config::Servers that have hosts that have routes that point to this Handler.
219 220 221 222 |
# File 'lib/mongrel2/handler.rb', line 219 def configured_servers hosts = self.configured_hosts return Mongrel2::Config::Server.where( id: hosts.select(:server_id) ) end |
#dispatch_request(request) ⇒ Object
Invoke a handler method appropriate for the given request
.
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/mongrel2/handler.rb', line 293 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 |
#handle(request) ⇒ Object
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.
347 348 349 350 351 352 353 |
# File 'lib/mongrel2/handler.rb', line 347 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) ⇒ Object
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.
411 412 413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/mongrel2/handler.rb', line 411 def handle_async_upload_start( request ) explanation = " If you wish to handle requests like this, either set your server's\n 'limits.content_length' setting to a higher value than %d, or override\n #handle_async_upload_start.\n END_MESSAGE\n\n self.log.warn \"Async upload from %s dropped.\" % [ request.remote_ip ]\n self.log.info( explanation )\n\n self.conn.reply_close( request )\n\n return nil\nend\n" % [ request.content_length ] |
#handle_disconnect(request) ⇒ Object
Handle a disconnect notice from Mongrel2 via the given request
. Its return value is ignored.
398 399 400 401 |
# File 'lib/mongrel2/handler.rb', line 398 def handle_disconnect( request ) self.log.info "Connection %p closed." % [ request.conn_id ] return nil end |
#handle_json(request) ⇒ Object
Handle a JSON message request
. If not overridden, JSON message (‘@route’) requests are ignored.
358 359 360 361 |
# File 'lib/mongrel2/handler.rb', line 358 def handle_json( request ) self.log.warn "Unhandled JSON message request (%p)" % [ request.headers.path ] return nil end |
#handle_websocket(request) ⇒ Object
Handle a WebSocket frame in request
. If not overridden, WebSocket connections are closed with a policy error status.
374 375 376 377 378 379 380 381 382 383 |
# File 'lib/mongrel2/handler.rb', line 374 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) ⇒ Object
Handle a WebSocket handshake HTTP request
. If not overridden, this method drops the connection.
388 389 390 391 392 393 |
# File 'lib/mongrel2/handler.rb', line 388 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) ⇒ Object
Handle an XML message request
. If not overridden, XML message (‘<route’) requests are ignored.
366 367 368 369 |
# File 'lib/mongrel2/handler.rb', line 366 def handle_xml( request ) self.log.warn "Unhandled XML message request (%p)" % [ request.headers.pack ] return nil end |
#handler_config ⇒ Object
Return the Mongrel2::Config::Handler that corresponds to this app’s appid, and its connection’s send_spec and recv_spec.
187 188 189 190 191 192 |
# File 'lib/mongrel2/handler.rb', line 187 def handler_config return self.configured_handlers.where( send_spec: self.conn.sub_addr, recv_spec: self.conn.pub_addr ).first end |
#inspect ⇒ Object
Returns a string containing a human-readable representation of the Handler suitable for debugging.
327 328 329 330 331 332 333 |
# File 'lib/mongrel2/handler.rb', line 327 def inspect return "#<%p:0x%016x conn: %p>" % [ self.class, self.object_id * 2, self.conn, ] end |
#on_socket_event(event) ⇒ Object
Reactor callback – handle an IO event.
258 259 260 261 262 263 264 265 266 267 |
# File 'lib/mongrel2/handler.rb', line 258 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 ⇒ Object
Restart the handler. You should override this if you want to re-establish database connections, flush caches, or other restart-ey stuff.
235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/mongrel2/handler.rb', line 235 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 ⇒ Object
Run the handler.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/mongrel2/handler.rb', line 169 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 ⇒ Object
Shut down the handler.
226 227 228 229 230 |
# File 'lib/mongrel2/handler.rb', line 226 def shutdown self.log.info "Shutting down." self.reactor.stop_polling @conn.close end |
#start_accepting_requests ⇒ Object
Start a loop, accepting a request and handling it.
251 252 253 254 |
# File 'lib/mongrel2/handler.rb', line 251 def start_accepting_requests self.log.info "Starting the request loop." self.reactor.start_polling( ignore_interrupts: true ) end |