Class: Mongrel::Configurator

Inherits:
Object
  • Object
show all
Defined in:
lib/mongrel/configurator.rb

Overview

Implements a simple DSL for configuring a Mongrel server for your purposes. More used by framework implementers to setup Mongrel how they like, but could be used by regular folks to add more things to an existing mongrel configuration.

It is used like this:

require 'mongrel'
config = Mongrel::Configurator.new :host => "127.0.0.1" do
  listener :port => 3000 do
    uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml"))
  end
  run
end

This will setup a simple DirHandler at the current directory and load additional mime types from mimy.yaml. The :host => “127.0.0.1” is actually not specific to the servers but just a hash of default parameters that all server or uri calls receive.

When you are inside the block after Mongrel::Configurator.new you can simply call functions that are part of Configurator (like server, uri, daemonize, etc) without having to refer to anything else. You can also call these functions on the resulting object directly for additional configuration.

A major thing about Configurator is that it actually lets you configure multiple listeners for any hosts and ports you want. These are kept in a map config.listeners so you can get to them.

  • :pid_file => Where to write the process ID.

Direct Known Subclasses

Rails::RailsConfigurator

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(defaults = {}, &block) ⇒ Configurator

You pass in initial defaults and then a block to continue configuring.



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/mongrel/configurator.rb', line 41

def initialize(defaults={}, &block)
  @listener = nil
  @listener_name = nil
  @listeners = {}
  @defaults = defaults
  @needs_restart = false
  @pid_file = defaults[:pid_file]

  if block
    cloaker(&block).bind(self).call
  end
end

Instance Attribute Details

#defaultsObject (readonly)

Returns the value of attribute defaults.



37
38
39
# File 'lib/mongrel/configurator.rb', line 37

def defaults
  @defaults
end

#listenersObject (readonly)

Returns the value of attribute listeners.



36
37
38
# File 'lib/mongrel/configurator.rb', line 36

def listeners
  @listeners
end

#needs_restartObject (readonly)

Returns the value of attribute needs_restart.



38
39
40
# File 'lib/mongrel/configurator.rb', line 38

def needs_restart
  @needs_restart
end

Instance Method Details

#change_privilege(user, group) ⇒ Object

Change privileges of the process to specified user and group.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mongrel/configurator.rb', line 55

def change_privilege(user, group)
  begin
    uid, gid = Process.euid, Process.egid
    target_uid = Etc.getpwnam(user).uid if user
    target_gid = Etc.getgrnam(group).gid if group

    if uid != target_uid or gid != target_gid
      log "Initiating groups for #{user.inspect}:#{group.inspect}."
      Process.initgroups(user, target_gid)
    
      log "Changing group to #{group.inspect}."
      Process::GID.change_privilege(target_gid)

      log "Changing user to #{user.inspect}." 
      Process::UID.change_privilege(target_uid)
    end
  rescue Errno::EPERM => e
    log "Couldn't change user and group to #{user.inspect}:#{group.inspect}: #{e.to_s}."
    log "Mongrel failed to start."
    exit 1
  end
end

#cloaker(&block) ⇒ Object

Do not call this. You were warned.



102
103
104
105
106
107
108
109
# File 'lib/mongrel/configurator.rb', line 102

def cloaker(&block)
  cloaking_class.class_eval do
    define_method :cloaker_, &block
    meth = instance_method( :cloaker_ )
    remove_method :cloaker_
    meth
  end
end

#cloaking_classObject

Generates a class for cloaking the current self and making the DSL nicer.



95
96
97
98
99
# File 'lib/mongrel/configurator.rb', line 95

def cloaking_class
  class << self
    self
  end
end

#daemonize(options = {}) ⇒ Object

Daemonizes the current Ruby script turning all the listeners into an actual “server” or detached process. You must call this before frameworks that open files as otherwise the files will be closed by this function.

Does not work for Win32 systems (the call is silently ignored).

Requires the following options or defaults:

  • :cwd => Directory to change to.

  • :log_file => Where to write STDOUT and STDERR.

It is safe to call this on win32 as it will only require the daemons gem/library if NOT win32.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/mongrel/configurator.rb', line 185

def daemonize(options={})
  ops = resolve_defaults(options)
  # save this for later since daemonize will hose it
  if RUBY_PLATFORM !~ /mswin/
    require 'daemons/daemonize'

    logfile = ops[:log_file]
    if logfile[0].chr != "/"
      logfile = File.join(ops[:cwd],logfile)
      if not File.exist?(File.dirname(logfile))
        log "!!! Log file directory not found at full path #{File.dirname(logfile)}.  Update your configuration to use a full path."
        exit 1
      end
    end

    Daemonize.daemonize(logfile)

    # change back to the original starting directory
    Dir.chdir(ops[:cwd])

  else
    log "WARNING: Win32 does not support daemon mode."
  end
end

#debug(location, what = [:access, :files, :objects, :threads, :rails]) ⇒ Object

Calling this before you register your URIs to the given location will setup a set of handlers that log open files, objects, and the parameters for each request. This helps you track common problems found in Rails applications that are either slow or become unresponsive after a little while.

You can pass an extra parameter what to indicate what you want to debug. For example, if you just want to dump rails stuff then do:

debug "/", what = [:rails]

And it will only produce the log/mongrel_debug/rails.log file. Available options are: :access, :files, :objects, :threads, :rails

NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick.



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/mongrel/configurator.rb', line 322

def debug(location, what = [:access, :files, :objects, :threads, :rails])
  require 'mongrel/debug'
  handlers = {
    :access => "/handlers/requestlog::access", 
    :files => "/handlers/requestlog::files", 
    :objects => "/handlers/requestlog::objects", 
    :threads => "/handlers/requestlog::threads",
    :rails => "/handlers/requestlog::params"
  }

  # turn on the debugging infrastructure, and ObjectTracker is a pig
  MongrelDbg.configure

  # now we roll through each requested debug type, turn it on and load that plugin
  what.each do |type| 
    MongrelDbg.begin_trace type 
    uri location, :handler => plugin(handlers[type])
  end
end

#joinObject

This method should actually be called outside of the Configurator block so that you can control it. In other words do it like: config.join.



302
303
304
# File 'lib/mongrel/configurator.rb', line 302

def join
  @listeners.values.each {|s| s.acceptor.join }
end

#listener(options = {}, &block) ⇒ Object

Starts a listener block. This is the only one that actually takes a block and then you make Configurator.uri calls in order to setup your URIs and handlers. If you write your Handlers as GemPlugins then you can use load_plugins and plugin to load them.

It expects the following options (or defaults):

  • :host => Host name to bind.

  • :port => Port to bind.

  • :num_processors => The maximum number of concurrent threads allowed.

  • :throttle => Time to pause (in hundredths of a second) between accepting clients.

  • :timeout => Time to wait (in seconds) before killing a stalled thread.

  • :user => User to change to, must have :group as well.

  • :group => Group to change to, must have :user as well.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/mongrel/configurator.rb', line 132

def listener(options={},&block)
  raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
  ops = resolve_defaults(options)
  ops[:num_processors] ||= 950
  ops[:throttle] ||= 0
  ops[:timeout] ||= 60

  @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i)
  @listener_name = "#{ops[:host]}:#{ops[:port]}"
  @listeners[@listener_name] = @listener

  if ops[:user] and ops[:group]
    change_privilege(ops[:user], ops[:group])
  end

  # Does the actual cloaking operation to give the new implicit self.
  if block
    cloaker(&block).bind(self).call
  end

  # all done processing this listener setup, reset implicit variables
  @listener = nil
  @listener_name = nil
end

#load_mime_map(file, mime = {}) ⇒ Object

Loads the MIME map file and checks that it is correct on loading. This is commonly passed to Mongrel::DirHandler or any framework handler that uses DirHandler to serve files. You can also include a set of default MIME types as additional settings. See Mongrel::DirHandler for how the MIME types map is organized.



247
248
249
250
251
252
253
254
255
# File 'lib/mongrel/configurator.rb', line 247

def load_mime_map(file, mime={})
  # configure any requested mime map
  mime = load_yaml(file, mime)

  # check all the mime types to make sure they are the right format
  mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }

  return mime
end

#load_plugins(options = {}) ⇒ Object

Uses the GemPlugin system to easily load plugins based on their gem dependencies. You pass in either an :includes => [] or :excludes => [] setting listing the names of plugins to include or exclude from the determining the dependencies.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/mongrel/configurator.rb', line 215

def load_plugins(options={})
  ops = resolve_defaults(options)

  load_settings = {}
  if ops[:includes]
    ops[:includes].each do |plugin|
      load_settings[plugin] = GemPlugin::INCLUDE
    end
  end

  if ops[:excludes]
    ops[:excludes].each do |plugin|
      load_settings[plugin] = GemPlugin::EXCLUDE
    end
  end

  GemPlugin::Manager.instance.load(load_settings)
end

#load_yaml(file, default = {}) ⇒ Object

Easy way to load a YAML file and apply default settings.



236
237
238
# File 'lib/mongrel/configurator.rb', line 236

def load_yaml(file, default={})
  default.merge(YAML.load_file(file))
end

#log(msg) ⇒ Object

Logs a simple message to STDERR (or the mongrel log if in daemon mode).



383
384
385
# File 'lib/mongrel/configurator.rb', line 383

def log(msg)
  STDERR.print "** ", msg, "\n"
end

#plugin(name, options = {}) ⇒ Object

Loads and creates a plugin for you based on the given name and configured with the selected options. The options are merged with the defaults prior to passing them in.



261
262
263
264
# File 'lib/mongrel/configurator.rb', line 261

def plugin(name, options={})
  ops = resolve_defaults(options)
  GemPlugin::Manager.instance.create(name, ops)
end

#redirect(from, pattern, replacement = nil, &block) ⇒ Object

Lets you do redirects easily as described in Mongrel::RedirectHandler. You use it inside the configurator like this:

redirect("/test", "/to/there") # simple
redirect("/to", /t/, 'w') # regexp
redirect("/hey", /(w+)/) {|match| ...}  # block


273
274
275
# File 'lib/mongrel/configurator.rb', line 273

def redirect(from, pattern, replacement = nil, &block)
  uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block)
end

#remove_pid_fileObject



78
79
80
# File 'lib/mongrel/configurator.rb', line 78

def remove_pid_file
  File.unlink(@pid_file) if @pid_file and File.exists?(@pid_file)
end

#resolve_defaults(options) ⇒ Object

This will resolve the given options against the defaults. Normally just used internally.



113
114
115
# File 'lib/mongrel/configurator.rb', line 113

def resolve_defaults(options)
  options.merge(@defaults)
end

#runObject

Works like a meta run method which goes through all the configured listeners. Use the Configurator.join method to prevent Ruby from exiting until each one is done.



280
281
282
283
284
285
286
# File 'lib/mongrel/configurator.rb', line 280

def run
  @listeners.each {|name,s| 
    s.run 
  }

  $mongrel_sleeper_thread = Thread.new { loop { sleep 1 } }
end

#run_config(script) ⇒ Object

Used to allow you to let users specify their own configurations inside your Configurator setup. You pass it a script name and it reads it in and does an eval on the contents passing in the right binding so they can put their own Configurator statements.



346
347
348
# File 'lib/mongrel/configurator.rb', line 346

def run_config(script)
  open(script) {|f| eval(f.read, proc {self}) }
end

#setup_signals(options = {}) ⇒ Object

Sets up the standard signal handlers that are used on most Ruby It only configures if the platform is not win32 and doesn’t do a HUP signal since this is typically framework specific.

Requires a :pid_file option given to Configurator.new to indicate a file to delete.

It sets the MongrelConfig.needs_restart attribute if the start command should reload. It’s up to you to detect this and do whatever is needed for a “restart”.

This command is safely ignored if the platform is win32 (with a warning)



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/mongrel/configurator.rb', line 360

def setup_signals(options={})
  ops = resolve_defaults(options)

  # forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C)
  trap("INT") { log "INT signal received."; stop(false) }

  # clean up the pid file always
  at_exit { remove_pid_file }

  if RUBY_PLATFORM !~ /mswin/
    # graceful shutdown
    trap("TERM") { log "TERM signal received."; stop }
    trap("USR1") { log "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"; $mongrel_debug_client = !$mongrel_debug_client }
    # restart
    trap("USR2") { log "USR2 signal received."; stop(true) }

    log "Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart)."
  else
    log "Signals ready.  INT => stop (no restart)."
  end
end

#stop(needs_restart = false, synchronous = false) ⇒ Object

Calls .stop on all the configured listeners so they stop processing requests (gracefully). By default it assumes that you don’t want to restart.



291
292
293
294
295
296
# File 'lib/mongrel/configurator.rb', line 291

def stop(needs_restart=false, synchronous=false)   
  @listeners.each do |name,s| 
    s.stop(synchronous)      
  end      
  @needs_restart = needs_restart
end

#uri(location, options = {}) ⇒ Object

Called inside a Configurator.listener block in order to add URI->handler mappings for that listener. Use this as many times as you like. It expects the following options or defaults:

  • :handler => HttpHandler – Handler to use for this location.

  • :in_front => true/false – Rather than appending, it prepends this handler.



165
166
167
168
# File 'lib/mongrel/configurator.rb', line 165

def uri(location, options={})
  ops = resolve_defaults(options)
  @listener.register(location, ops[:handler], ops[:in_front])
end

#write_pid_fileObject

Writes the PID file if we’re not on Windows.



83
84
85
86
87
88
89
90
91
92
# File 'lib/mongrel/configurator.rb', line 83

def write_pid_file
  if RUBY_PLATFORM !~ /mswin/
    log "Writing PID file to #{@pid_file}"
    open(@pid_file,"w") {|f| f.write(Process.pid) }
    open(@pid_file,"w") do |f|
      f.write(Process.pid)
      File.chmod(0644, @pid_file)
    end      
  end
end