| 1 |
require 'yaml' |
|---|
| 2 |
require 'etc' |
|---|
| 3 |
|
|---|
| 4 |
module Mongrel |
|---|
| 5 |
# Implements a simple DSL for configuring a Mongrel server for your |
|---|
| 6 |
# purposes. More used by framework implementers to setup Mongrel |
|---|
| 7 |
# how they like, but could be used by regular folks to add more things |
|---|
| 8 |
# to an existing mongrel configuration. |
|---|
| 9 |
# |
|---|
| 10 |
# It is used like this: |
|---|
| 11 |
# |
|---|
| 12 |
# require 'mongrel' |
|---|
| 13 |
# config = Mongrel::Configurator.new :host => "127.0.0.1" do |
|---|
| 14 |
# listener :port => 3000 do |
|---|
| 15 |
# uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml")) |
|---|
| 16 |
# end |
|---|
| 17 |
# run |
|---|
| 18 |
# end |
|---|
| 19 |
# |
|---|
| 20 |
# This will setup a simple DirHandler at the current directory and load additional |
|---|
| 21 |
# mime types from mimy.yaml. The :host => "127.0.0.1" is actually not |
|---|
| 22 |
# specific to the servers but just a hash of default parameters that all |
|---|
| 23 |
# server or uri calls receive. |
|---|
| 24 |
# |
|---|
| 25 |
# When you are inside the block after Mongrel::Configurator.new you can simply |
|---|
| 26 |
# call functions that are part of Configurator (like server, uri, daemonize, etc) |
|---|
| 27 |
# without having to refer to anything else. You can also call these functions on |
|---|
| 28 |
# the resulting object directly for additional configuration. |
|---|
| 29 |
# |
|---|
| 30 |
# A major thing about Configurator is that it actually lets you configure |
|---|
| 31 |
# multiple listeners for any hosts and ports you want. These are kept in a |
|---|
| 32 |
# map config.listeners so you can get to them. |
|---|
| 33 |
# |
|---|
| 34 |
# * :pid_file => Where to write the process ID. |
|---|
| 35 |
class Configurator |
|---|
| 36 |
attr_reader :listeners |
|---|
| 37 |
attr_reader :defaults |
|---|
| 38 |
attr_reader :needs_restart |
|---|
| 39 |
|
|---|
| 40 |
# You pass in initial defaults and then a block to continue configuring. |
|---|
| 41 |
def initialize(defaults={}, &block) |
|---|
| 42 |
@listener = nil |
|---|
| 43 |
@listener_name = nil |
|---|
| 44 |
@listeners = {} |
|---|
| 45 |
@defaults = defaults |
|---|
| 46 |
@needs_restart = false |
|---|
| 47 |
@pid_file = defaults[:pid_file] |
|---|
| 48 |
|
|---|
| 49 |
if block |
|---|
| 50 |
cloaker(&block).bind(self).call |
|---|
| 51 |
end |
|---|
| 52 |
end |
|---|
| 53 |
|
|---|
| 54 |
# Change privileges of the process to specified user and group. |
|---|
| 55 |
def change_privilege(user, group) |
|---|
| 56 |
begin |
|---|
| 57 |
uid, gid = Process.euid, Process.egid |
|---|
| 58 |
target_uid = Etc.getpwnam(user).uid if user |
|---|
| 59 |
target_gid = Etc.getgrnam(group).gid if group |
|---|
| 60 |
|
|---|
| 61 |
if uid != target_uid or gid != target_gid |
|---|
| 62 |
Mongrel.log("Initiating groups for #{user.inspect}:#{group.inspect}.") |
|---|
| 63 |
Process.initgroups(user, target_gid) |
|---|
| 64 |
|
|---|
| 65 |
Mongrel.log("Changing group to #{group.inspect}.") |
|---|
| 66 |
Process::GID.change_privilege(target_gid) |
|---|
| 67 |
|
|---|
| 68 |
Mongrel.log("Changing user to #{user.inspect}." ) |
|---|
| 69 |
Process::UID.change_privilege(target_uid) |
|---|
| 70 |
end |
|---|
| 71 |
rescue Errno::EPERM => e |
|---|
| 72 |
Mongrel.log(:critical, "Couldn't change user and group to #{user.inspect}:#{group.inspect}: #{e.to_s}.") |
|---|
| 73 |
Mongrel.log(:critical, "Mongrel failed to start.") |
|---|
| 74 |
exit 1 |
|---|
| 75 |
end |
|---|
| 76 |
end |
|---|
| 77 |
|
|---|
| 78 |
def remove_pid_file |
|---|
| 79 |
File.unlink(@pid_file) if @pid_file and File.exists?(@pid_file) |
|---|
| 80 |
end |
|---|
| 81 |
|
|---|
| 82 |
# Writes the PID file if we're not on Windows. |
|---|
| 83 |
def write_pid_file |
|---|
| 84 |
unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ |
|---|
| 85 |
Mongrel.log("Writing PID file to #{@pid_file}") |
|---|
| 86 |
open(@pid_file,"w") {|f| f.write(Process.pid) } |
|---|
| 87 |
open(@pid_file,"w") do |f| |
|---|
| 88 |
f.write(Process.pid) |
|---|
| 89 |
File.chmod(0644, @pid_file) |
|---|
| 90 |
end |
|---|
| 91 |
end |
|---|
| 92 |
end |
|---|
| 93 |
|
|---|
| 94 |
# Generates a class for cloaking the current self and making the DSL nicer. |
|---|
| 95 |
def cloaking_class |
|---|
| 96 |
class << self |
|---|
| 97 |
self |
|---|
| 98 |
end |
|---|
| 99 |
end |
|---|
| 100 |
|
|---|
| 101 |
# Do not call this. You were warned. |
|---|
| 102 |
def cloaker(&block) |
|---|
| 103 |
cloaking_class.class_eval do |
|---|
| 104 |
define_method :cloaker_, &block |
|---|
| 105 |
meth = instance_method( :cloaker_ ) |
|---|
| 106 |
remove_method :cloaker_ |
|---|
| 107 |
meth |
|---|
| 108 |
end |
|---|
| 109 |
end |
|---|
| 110 |
|
|---|
| 111 |
# This will resolve the given options against the defaults. |
|---|
| 112 |
# Normally just used internally. |
|---|
| 113 |
def resolve_defaults(options) |
|---|
| 114 |
options.merge(@defaults) |
|---|
| 115 |
end |
|---|
| 116 |
|
|---|
| 117 |
# Starts a listener block. This is the only one that actually takes |
|---|
| 118 |
# a block and then you make Configurator.uri calls in order to setup |
|---|
| 119 |
# your URIs and handlers. If you write your Handlers as GemPlugins |
|---|
| 120 |
# then you can use load_plugins and plugin to load them. |
|---|
| 121 |
# |
|---|
| 122 |
# It expects the following options (or defaults): |
|---|
| 123 |
# |
|---|
| 124 |
# * :host => Host name to bind. |
|---|
| 125 |
# * :port => Port to bind. |
|---|
| 126 |
# * :num_processors => The maximum number of concurrent threads allowed. |
|---|
| 127 |
# * :throttle => Time to pause (in hundredths of a second) between accepting clients. |
|---|
| 128 |
# * :timeout => Time to wait (in seconds) before killing a stalled thread. |
|---|
| 129 |
# * :user => User to change to, must have :group as well. |
|---|
| 130 |
# * :group => Group to change to, must have :user as well. |
|---|
| 131 |
# |
|---|
| 132 |
def listener(options={},&block) |
|---|
| 133 |
raise "Cannot call listener inside another listener block." if (@listener or @listener_name) |
|---|
| 134 |
opts = resolve_defaults(options) |
|---|
| 135 |
opts[:num_processors] ||= 950 |
|---|
| 136 |
opts[:throttle] ||= 0 |
|---|
| 137 |
opts[:timeout] ||= 60 |
|---|
| 138 |
|
|---|
| 139 |
@listener = Mongrel::HttpServer.new( |
|---|
| 140 |
opts[:host], opts[:port].to_i, opts[:num_processors].to_i, |
|---|
| 141 |
opts[:throttle].to_i, opts[:timeout].to_i, |
|---|
| 142 |
opts[:log], opts[:log_level] |
|---|
| 143 |
) |
|---|
| 144 |
@listener_name = "#{opts[:host]}:#{opts[:port]}" |
|---|
| 145 |
@listeners[@listener_name] = @listener |
|---|
| 146 |
|
|---|
| 147 |
if opts[:user] and opts[:group] |
|---|
| 148 |
change_privilege(opts[:user], opts[:group]) |
|---|
| 149 |
end |
|---|
| 150 |
|
|---|
| 151 |
# Does the actual cloaking operation to give the new implicit self. |
|---|
| 152 |
if block |
|---|
| 153 |
cloaker(&block).bind(self).call |
|---|
| 154 |
end |
|---|
| 155 |
|
|---|
| 156 |
# all done processing this listener setup, reset implicit variables |
|---|
| 157 |
@listener = nil |
|---|
| 158 |
@listener_name = nil |
|---|
| 159 |
end |
|---|
| 160 |
|
|---|
| 161 |
|
|---|
| 162 |
# Called inside a Configurator.listener block in order to |
|---|
| 163 |
# add URI->handler mappings for that listener. Use this as |
|---|
| 164 |
# many times as you like. It expects the following options |
|---|
| 165 |
# or defaults: |
|---|
| 166 |
# |
|---|
| 167 |
# * :handler => HttpHandler -- Handler to use for this location. |
|---|
| 168 |
# * :in_front => true/false -- Rather than appending, it prepends this handler. |
|---|
| 169 |
def uri(location, options={}) |
|---|
| 170 |
opts = resolve_defaults(options) |
|---|
| 171 |
@listener.register(location, opts[:handler], opts[:in_front]) |
|---|
| 172 |
end |
|---|
| 173 |
|
|---|
| 174 |
|
|---|
| 175 |
# Daemonizes the current Ruby script turning all the |
|---|
| 176 |
# listeners into an actual "server" or detached process. |
|---|
| 177 |
# You must call this *before* frameworks that open files |
|---|
| 178 |
# as otherwise the files will be closed by this function. |
|---|
| 179 |
# |
|---|
| 180 |
# Does not work for Win32 systems (the call is silently ignored). |
|---|
| 181 |
# |
|---|
| 182 |
# Requires the following options or defaults: |
|---|
| 183 |
# |
|---|
| 184 |
# * :cwd => Directory to change to. |
|---|
| 185 |
# * :log_file => Where to write STDOUT and STDERR. |
|---|
| 186 |
# |
|---|
| 187 |
# It is safe to call this on win32 as it will only require the daemons |
|---|
| 188 |
# gem/library if NOT win32. |
|---|
| 189 |
def daemonize(options={}) |
|---|
| 190 |
opts = resolve_defaults(options) |
|---|
| 191 |
# save this for later since daemonize will hose it |
|---|
| 192 |
unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ |
|---|
| 193 |
require 'daemons/daemonize' |
|---|
| 194 |
|
|---|
| 195 |
logfile = opts[:log_file] |
|---|
| 196 |
if logfile[0].chr != "/" |
|---|
| 197 |
logfile = File.join(opts[:cwd],logfile) |
|---|
| 198 |
if not File.exist?(File.dirname(logfile)) |
|---|
| 199 |
Mongrel.log(:critical, "!!! Log file directory not found at full path #{File.dirname(logfile)}. Update your configuration to use a full path.") |
|---|
| 200 |
exit 1 |
|---|
| 201 |
end |
|---|
| 202 |
end |
|---|
| 203 |
|
|---|
| 204 |
Daemonize.daemonize(logfile) |
|---|
| 205 |
|
|---|
| 206 |
# change back to the original starting directory |
|---|
| 207 |
Dir.chdir(opts[:cwd]) |
|---|
| 208 |
|
|---|
| 209 |
else |
|---|
| 210 |
Mongrel.log(:warning, "WARNING: Win32 does not support daemon mode.") |
|---|
| 211 |
end |
|---|
| 212 |
end |
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
# Uses the GemPlugin system to easily load plugins based on their |
|---|
| 216 |
# gem dependencies. You pass in either an :includes => [] or |
|---|
| 217 |
# :excludes => [] setting listing the names of plugins to include |
|---|
| 218 |
# or exclude from the determining the dependencies. |
|---|
| 219 |
def load_plugins(options={}) |
|---|
| 220 |
opts = resolve_defaults(options) |
|---|
| 221 |
|
|---|
| 222 |
load_settings = {} |
|---|
| 223 |
if opts[:includes] |
|---|
| 224 |
opts[:includes].each do |plugin| |
|---|
| 225 |
load_settings[plugin] = GemPlugin::INCLUDE |
|---|
| 226 |
end |
|---|
| 227 |
end |
|---|
| 228 |
|
|---|
| 229 |
if opts[:excludes] |
|---|
| 230 |
opts[:excludes].each do |plugin| |
|---|
| 231 |
load_settings[plugin] = GemPlugin::EXCLUDE |
|---|
| 232 |
end |
|---|
| 233 |
end |
|---|
| 234 |
|
|---|
| 235 |
GemPlugin::Manager.instance.load(load_settings) |
|---|
| 236 |
end |
|---|
| 237 |
|
|---|
| 238 |
|
|---|
| 239 |
# Easy way to load a YAML file and apply default settings. |
|---|
| 240 |
def load_yaml(file, default={}) |
|---|
| 241 |
default.merge(YAML.load_file(file)) |
|---|
| 242 |
end |
|---|
| 243 |
|
|---|
| 244 |
|
|---|
| 245 |
# Loads the MIME map file and checks that it is correct |
|---|
| 246 |
# on loading. This is commonly passed to Mongrel::DirHandler |
|---|
| 247 |
# or any framework handler that uses DirHandler to serve files. |
|---|
| 248 |
# You can also include a set of default MIME types as additional |
|---|
| 249 |
# settings. See Mongrel::DirHandler for how the MIME types map |
|---|
| 250 |
# is organized. |
|---|
| 251 |
def load_mime_map(file, mime={}) |
|---|
| 252 |
# configure any requested mime map |
|---|
| 253 |
mime = load_yaml(file, mime) |
|---|
| 254 |
|
|---|
| 255 |
# check all the mime types to make sure they are the right format |
|---|
| 256 |
mime.each {|k,v| Mongrel.log(:warning, "WARNING: MIME type #{k} must start with '.'") if k.index(".") != 0 } |
|---|
| 257 |
|
|---|
| 258 |
return mime |
|---|
| 259 |
end |
|---|
| 260 |
|
|---|
| 261 |
|
|---|
| 262 |
# Loads and creates a plugin for you based on the given |
|---|
| 263 |
# name and configured with the selected options. The options |
|---|
| 264 |
# are merged with the defaults prior to passing them in. |
|---|
| 265 |
def plugin(name, options={}) |
|---|
| 266 |
opts = resolve_defaults(options) |
|---|
| 267 |
GemPlugin::Manager.instance.create(name, opts) |
|---|
| 268 |
end |
|---|
| 269 |
|
|---|
| 270 |
# Lets you do redirects easily as described in Mongrel::RedirectHandler. |
|---|
| 271 |
# You use it inside the configurator like this: |
|---|
| 272 |
# |
|---|
| 273 |
# redirect("/test", "/to/there") # simple |
|---|
| 274 |
# redirect("/to", /t/, 'w') # regexp |
|---|
| 275 |
# redirect("/hey", /(w+)/) {|match| ...} # block |
|---|
| 276 |
# |
|---|
| 277 |
def redirect(from, pattern, replacement = nil, &block) |
|---|
| 278 |
uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block) |
|---|
| 279 |
end |
|---|
| 280 |
|
|---|
| 281 |
# Works like a meta run method which goes through all the |
|---|
| 282 |
# configured listeners. Use the Configurator.join method |
|---|
| 283 |
# to prevent Ruby from exiting until each one is done. |
|---|
| 284 |
def run |
|---|
| 285 |
@listeners.each {|name,s| |
|---|
| 286 |
s.run |
|---|
| 287 |
} |
|---|
| 288 |
|
|---|
| 289 |
$mongrel_sleeper_thread = Thread.new { loop { sleep 1 } } |
|---|
| 290 |
end |
|---|
| 291 |
|
|---|
| 292 |
# Calls .stop on all the configured listeners so they |
|---|
| 293 |
# stop processing requests (gracefully). By default it |
|---|
| 294 |
# assumes that you don't want to restart. |
|---|
| 295 |
def stop(needs_restart=false, synchronous=false) |
|---|
| 296 |
@listeners.each do |name,s| |
|---|
| 297 |
s.stop(synchronous) |
|---|
| 298 |
end |
|---|
| 299 |
@needs_restart = needs_restart |
|---|
| 300 |
end |
|---|
| 301 |
|
|---|
| 302 |
|
|---|
| 303 |
# This method should actually be called *outside* of the |
|---|
| 304 |
# Configurator block so that you can control it. In other words |
|---|
| 305 |
# do it like: config.join. |
|---|
| 306 |
def join |
|---|
| 307 |
@listeners.values.each {|s| s.acceptor.join } |
|---|
| 308 |
end |
|---|
| 309 |
|
|---|
| 310 |
|
|---|
| 311 |
# Calling this before you register your URIs to the given location |
|---|
| 312 |
# will setup a set of handlers that log open files, objects, and the |
|---|
| 313 |
# parameters for each request. This helps you track common problems |
|---|
| 314 |
# found in Rails applications that are either slow or become unresponsive |
|---|
| 315 |
# after a little while. |
|---|
| 316 |
# |
|---|
| 317 |
# You can pass an extra parameter *what* to indicate what you want to |
|---|
| 318 |
# debug. For example, if you just want to dump rails stuff then do: |
|---|
| 319 |
# |
|---|
| 320 |
# debug "/", what = [:rails] |
|---|
| 321 |
# |
|---|
| 322 |
# And it will only produce the log/mongrel_debug/rails.log file. |
|---|
| 323 |
# Available options are: :access, :files, :objects, :threads, :rails |
|---|
| 324 |
# |
|---|
| 325 |
# NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick. |
|---|
| 326 |
def debug(location, what = [:access, :files, :objects, :threads, :rails]) |
|---|
| 327 |
require 'mongrel/debug' |
|---|
| 328 |
handlers = { |
|---|
| 329 |
:access => "/handlers/requestlog::access", |
|---|
| 330 |
:files => "/handlers/requestlog::files", |
|---|
| 331 |
:objects => "/handlers/requestlog::objects", |
|---|
| 332 |
:threads => "/handlers/requestlog::threads", |
|---|
| 333 |
:rails => "/handlers/requestlog::params" |
|---|
| 334 |
} |
|---|
| 335 |
|
|---|
| 336 |
# turn on the debugging infrastructure, and ObjectTracker is a pig |
|---|
| 337 |
MongrelDbg.configure |
|---|
| 338 |
|
|---|
| 339 |
# now we roll through each requested debug type, turn it on and load that plugin |
|---|
| 340 |
what.each do |type| |
|---|
| 341 |
MongrelDbg.begin_trace type |
|---|
| 342 |
uri location, :handler => plugin(handlers[type]) |
|---|
| 343 |
end |
|---|
| 344 |
end |
|---|
| 345 |
|
|---|
| 346 |
# Used to allow you to let users specify their own configurations |
|---|
| 347 |
# inside your Configurator setup. You pass it a script name and |
|---|
| 348 |
# it reads it in and does an eval on the contents passing in the right |
|---|
| 349 |
# binding so they can put their own Configurator statements. |
|---|
| 350 |
def run_config(script) |
|---|
| 351 |
open(script) {|f| eval(f.read, proc {self}.binding) } |
|---|
| 352 |
end |
|---|
| 353 |
|
|---|
| 354 |
# Sets up the standard signal handlers that are used on most Ruby |
|---|
| 355 |
# It only configures if the platform is not win32 and doesn't do |
|---|
| 356 |
# a HUP signal since this is typically framework specific. |
|---|
| 357 |
# |
|---|
| 358 |
# Requires a :pid_file option given to Configurator.new to indicate a file to delete. |
|---|
| 359 |
# It sets the MongrelConfig.needs_restart attribute if |
|---|
| 360 |
# the start command should reload. It's up to you to detect this |
|---|
| 361 |
# and do whatever is needed for a "restart". |
|---|
| 362 |
# |
|---|
| 363 |
# This command is safely ignored if the platform is win32 (with a warning) |
|---|
| 364 |
def setup_signals(options={}) |
|---|
| 365 |
opts = resolve_defaults(options) |
|---|
| 366 |
|
|---|
| 367 |
# forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C) |
|---|
| 368 |
trap("INT") { Mongrel.log(:notice, "INT signal received."); stop(false) } |
|---|
| 369 |
|
|---|
| 370 |
# always clean up the pid file |
|---|
| 371 |
at_exit { remove_pid_file } |
|---|
| 372 |
|
|---|
| 373 |
unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ |
|---|
| 374 |
# graceful shutdown |
|---|
| 375 |
trap("TERM") { Mongrel.log(:notice, "TERM signal received."); stop } |
|---|
| 376 |
# debug mode |
|---|
| 377 |
trap("USR1") { Mongrel.log(:notice, "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"); $mongrel_debug_client = !$mongrel_debug_client } |
|---|
| 378 |
# restart |
|---|
| 379 |
trap("USR2") { Mongrel.log(:notice, "USR2 signal received."); stop(true) } |
|---|
| 380 |
|
|---|
| 381 |
Mongrel.log(:notice, "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart).") |
|---|
| 382 |
else |
|---|
| 383 |
Mongrel.log(:notice, "Signals ready. INT => stop (no restart).") |
|---|
| 384 |
end |
|---|
| 385 |
end |
|---|
| 386 |
|
|---|
| 387 |
end |
|---|
| 388 |
|
|---|
| 389 |
end |
|---|