Class: Arrow::Dispatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/arrow/dispatcher.rb

Overview

The Arrow::Dispatcher class – the mod_ruby handler frontend for Arrow.

Synopsis

Simple configuration:

RubyRequire ‘arrow’

RubyChildInitHandler "Arrow::Dispatcher.create( 'myapp.yaml' )"

<Location /arrow>
    Handler ruby-object

RubyHandler Arrow::Dispatcher.instance </Location>

More-complex setup; run two Arrow dispatchers with different configurations from different Locations:

RubyRequire ‘arrow’

RubyChildInitHandler "Arrow::Dispatcher.create( :myapp => 'myapp.yml', :help => 'help.yml' )"

<Location /myapp>

Handler ruby-object RubyHandler Arrow::Dispatcher.instance(:myapp) </Location>

<Location /help>

Handler ruby-object RubyHandler Arrow::Dispatcher.instance(:help) </Location>

Same thing, but use a YAML file to control the dispatchers and where their configs are:

RubyRequire ‘arrow’

RubyChildInitHandler "Arrow.load_dispatchers('/Library/WebServer/arrow-hosts.yml')"

<Location /myapp>

Handler ruby-object RubyHandler Arrow::Dispatcher.instance(:myapp) </Location>

<Location /help>

Handler ruby-object RubyHandler Arrow::Dispatcher.instance(:help) </Location>

arrow-hosts.yml:

myapp:
  /some/directory/myapp.yml
help:
  /other/directory/help.yml

Authors

Please see the file LICENSE in the top-level directory for licensing details.

Constant Summary collapse

@@Instance =

C L A S S M E T H O D S

{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Object

deprecate_class_method, deprecate_method, inherited

Constructor Details

#initialize(name, config) ⇒ Dispatcher

Set up an Arrow::Dispatcher object based on the specified config (an Arrow::Config object).



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/arrow/dispatcher.rb', line 224

def initialize( name, config )
	@name = name
	@config = config

	@broker = Arrow::Broker.new( config )
	self.configure( config )
rescue ::Exception => err
	msg = "%s while creating dispatcher: %s\n%s" %
		[ err.class.name, err.message, err.backtrace.join("\n\t") ]
	self.log.error( msg )
	msg.gsub!( /%/, '%%' )
	Apache.request.server.log_crit( msg ) unless !defined?( Apache )
end

Instance Attribute Details

#nameObject (readonly)

The key used to indentify this dispatcher



244
245
246
# File 'lib/arrow/dispatcher.rb', line 244

def name
  @name
end

Class Method Details

.create(configspec) ⇒ Object

Set up one or more new Arrow::Dispatcher objects. The configspec argument can either be the path to a config file, or a hash of config files. See the .instance method for more about how to use this method.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/arrow/dispatcher.rb', line 113

def self::create( configspec )

	# Normalize configurations. Expected either a configfile path in a
	# String, or a Hash of configfiles
	case configspec
	when String
		configs = { :__default__ => configspec }
	when Hash
		configs = configspec
	else
		raise ArgumentError, "Invalid config hash %p" % [configspec]
	end

	# Create the dispatchers and return the first one to support the
	# old-style create, i.e.,
	#   dispatcher = Arrow::Dispatcher.create( configfile )
	@@Instance = create_configured_dispatchers( configs )
	@@Instance.values.first
rescue ::Exception => err

	# Try to log fatal errors to both the Apache server log and a crashfile
	# before passing the exception along.
	errmsg = "%s failed to start (%s): %s: %s" % [
		self.name,
		err.class.name,
		err.message,
		err.backtrace.join("\n  ")
	]

	logfile = File.join( Dir.tmpdir, "arrow-fatal.log.#{$$}" )
	File.open( logfile, IO::WRONLY|IO::TRUNC|IO::CREAT ) {|ofh|
		ofh.puts( errmsg )
		ofh.flush
	}

	if defined?( Apache )
		Apache.request.server.log_crit( errmsg )
	end

	Kernel.raise( err )
end

.create_configured_dispatchers(configspec) ⇒ Object

Create dispatchers for the config files given in configspec and return them in a Hash keyed by both the configname key and the expanded path to the configuration file.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/arrow/dispatcher.rb', line 184

def self::create_configured_dispatchers( configspec )
	instances = {}

	# Load a dispatcher for each config
	configspec.each do |key, configfile|

		# Normalize the path to the config file and make sure it's not
		# loaded yet. If it is, link it to the current key and skip to the
		# next.
		configfile = File.expand_path( configfile )
		if instances.key?( configfile )
			instances[ key ] = instances[ configfile ]
			next
		end

		# If a config file is given, load it. If it's not, just use the
		# default config.
		if configfile
			config = Arrow::Config.load( configfile )
		else
			config = Arrow::Config.new
		end

		# Create a dispatcher and put it in the table by both its key and
		# the normalized path to its configfile.
		msg = "Creating dispatcher %p from %p" % [ key, configfile ]
		Apache.request.server.log_notice( msg ) if defined?( Apache )
		instances[ key ] = instances[ configfile ] = new( key, config )
	end

	return instances
end

.create_from_hosts_file(hosts_file) ⇒ Object

Create one or more dispatchers from the specified hosts_file, which is a YAML file that maps arrow configurations onto a symbol that can be used to refer to it.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/arrow/dispatcher.rb', line 159

def self::create_from_hosts_file( hosts_file )
	configs = nil

	if hosts_file.respond_to?( :read )
		configs = YAML.load( hosts_file.read ) 
	else
		hosts_file.untaint
		configs = YAML.load_file( hosts_file )
	end

	# Convert the keys to Symbols and the values to untainted Strings.
	configs.each do |key,config|
		sym = key.to_s.dup.untaint.to_sym
		configs[ sym ] = configs.delete( key )
		configs[ sym ].untaint
	end

	@@Instance = self.create_configured_dispatchers( configs )
	return @@Instance
end

.instance(key = :__default__) ⇒ Object

Get the instance of the Dispatcher set up under the given key, which can either be a Symbol or a String containing the path to a configfile. If no key is given, it defaults to :__default__, which is the key assigned when .create is given just a configfile argument.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/arrow/dispatcher.rb', line 89

def self::instance( key=:__default__ )
	rval = nil

	# Fetch the instance which corresponds to the given key
	if key.is_a?( Symbol )
		Arrow::Logger.debug "Returning instance for key %p (one of %p): %p" %
			[key, @@Instance.keys, @@Instance[key]]
		rval = @@Instance[ key ]
	else
		Arrow::Logger.debug "Returning instance for configfile %p" % [key]
		configfile = File.expand_path( key )
		self.create( configfile )
		rval = @@Instance[ configfile ]
	end

	# Return either a configured Dispatcher instance or a FallbackHandler if
	# no Dispatcher corresponds to the given key.
	return rval || Arrow::FallbackHandler.new( key, @@Instance )
end

Instance Method Details

#child_init(req) ⇒ Object

Child init mod_ruby handler



269
270
271
272
# File 'lib/arrow/dispatcher.rb', line 269

def child_init( req ) # :nodoc
    self.log.notice "Dispatcher configured for %s" % [ req.server.hostname ]
    return Apache::OK
end

#configure(config) ⇒ Object

(Re)configure the dispatcher based on the values in the given config (an Arrow::Config object).



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/arrow/dispatcher.rb', line 249

def configure( config )
	self.log.notice "Configuring a dispatcher for '%s' from '%s': child server %d" %
		[ Apache.request.server.hostname, config.name, Process.pid ]

       # Configure any modules that have mixed in Configurability
	if defined?( Apache )
		require 'apache/logger'
		Configurability.logger = Logger.new( Apache::LogDevice.new )
		Configurability.logger.formatter = Apache::LogFormatter.new
	else
		Configurability.reset_logger
	end

      	Configurability.configure_objects( config )
end

#handler(req) ⇒ Object

The content handler method. Dispatches requests to registered applets based on the requests PATH_INFO.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/arrow/dispatcher.rb', line 277

def handler( req )
	self.log.info "--- Dispatching request %p ---------------" % [req]
	self.log.debug "Request headers are: %s" % [untable(req.headers_in)]

	if (( reason = @config.changed_reason ))
		self.log.notice "** Reloading configuration: #{reason} ***"
		@config.reload
		@broker = Arrow::Broker.new( @config )
		self.configure( @config )
	end

	if ! @broker
		self.log.error "Fatal: No broker."
		return Apache::SERVER_ERROR
	end

	txn = Arrow::Transaction.new( req, @config, @broker )

	self.log.debug "Delegating transaction %p" % [txn]
	unless output = @broker.delegate( txn )
		self.log.info "Declining transaction (Applets returned: %p)" % output
		return Apache::DECLINED
	end

	# If the transaction succeeded, set up the Apache::Request object, add
	# headers, add session state, etc. If it failed, log the failure and let
	# the status be returned as-is.
	response_body = nil
	self.log.debug "Transaction has status %d" % [txn.status]

	# Render the output before anything else, as there might be
	# session/header manipulation left to be done somewhere in the
	# render. If the response is true, the applets have handled output
	# themselves.
	if output && output != true
		rendertime = Benchmark.measure do
			response_body = output.to_s
		end
		self.log.debug "Output render time: %s" %
			rendertime.format( '%8.4us usr %8.4ys sys %8.4ts wall %8.4r' )
		req.headers_out['content-length'] = response_body.length.to_s unless
			req.headers_out['content-length']
	end

	# If the transaction has a session, save it
	txn.session.save if txn.session?

	# Add cookies to the response headers
	txn.add_cookie_headers

	self.log.debug "HTTP response status is: %d" % [txn.status]
	self.log.debug "Response headers were: %s" % [untable(req.headers_out)]
	txn.send_http_header
	txn.print( response_body ) if response_body

	self.log.info "--- Done with request %p (%s)---------------" % 
		[ req, req.status_line ]

	req.sync = true
	return txn.handler_status
rescue ::Exception => err
	self.log.error "Dispatcher caught an unhandled %s: %s:\n\t%s" %
		[ err.class.name, err.message, err.backtrace.join("\n\t") ]
	return Apache::SERVER_ERROR

ensure
	# Make sure session locks are released
	txn.session.finish if txn && txn.session?
end

#inspectObject

Return a human-readable representation of the receiver as a String.



349
350
351
352
353
354
355
# File 'lib/arrow/dispatcher.rb', line 349

def inspect
	return "#<%s:0x%x config: %s>" % [
		self.class.name,
		self.object_id,
		@config.name,
	]
end

#untable(table) ⇒ Object



358
359
360
361
362
363
364
365
# File 'lib/arrow/dispatcher.rb', line 358

def untable( table )
	lines = []
	table.each do |k,v|
		lines << "%s: %s" % [ k, v ]
	end
	
	return lines.join( "; " )
end