root/trunk/lib/mongrel/configurator.rb

Revision 926, 13.9 kB (checked in by filipe, 2 years ago)

eval in ruby1.9 do not accept proc objects anymore, so now we call proc {self}.binding . The behavior is the same as before (because in ruby1.8 this was kind of called inside eval anyway), but we have the benefit of run in 1.8 and 1.9.

Line 
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
Note: See TracBrowser for help on using the browser.