freels-mongrel 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +12 -0
  2. data/COPYING +55 -0
  3. data/LICENSE +55 -0
  4. data/Manifest +70 -0
  5. data/README +74 -0
  6. data/Rakefile +237 -0
  7. data/TODO +4 -0
  8. data/bin/mongrel_rails +284 -0
  9. data/examples/builder.rb +29 -0
  10. data/examples/camping/README +3 -0
  11. data/examples/camping/blog.rb +294 -0
  12. data/examples/camping/tepee.rb +149 -0
  13. data/examples/httpd.conf +474 -0
  14. data/examples/mime.yaml +3 -0
  15. data/examples/mongrel.conf +9 -0
  16. data/examples/mongrel_simple_ctrl.rb +92 -0
  17. data/examples/mongrel_simple_service.rb +116 -0
  18. data/examples/monitrc +57 -0
  19. data/examples/random_thrash.rb +19 -0
  20. data/examples/simpletest.rb +52 -0
  21. data/examples/webrick_compare.rb +20 -0
  22. data/ext/http11/ext_help.h +15 -0
  23. data/ext/http11/extconf.rb +6 -0
  24. data/ext/http11/http11.c +527 -0
  25. data/ext/http11/http11_parser.c +1216 -0
  26. data/ext/http11/http11_parser.h +49 -0
  27. data/ext/http11/http11_parser.java.rl +171 -0
  28. data/ext/http11/http11_parser.rl +165 -0
  29. data/ext/http11/http11_parser_common.rl +55 -0
  30. data/ext/http11_java/Http11Service.java +13 -0
  31. data/ext/http11_java/org/jruby/mongrel/Http11.java +266 -0
  32. data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
  33. data/lib/mongrel.rb +359 -0
  34. data/lib/mongrel/camping.rb +107 -0
  35. data/lib/mongrel/cgi.rb +182 -0
  36. data/lib/mongrel/command.rb +220 -0
  37. data/lib/mongrel/configurator.rb +389 -0
  38. data/lib/mongrel/const.rb +114 -0
  39. data/lib/mongrel/debug.rb +203 -0
  40. data/lib/mongrel/gems.rb +22 -0
  41. data/lib/mongrel/handlers.rb +472 -0
  42. data/lib/mongrel/header_out.rb +28 -0
  43. data/lib/mongrel/http_request.rb +155 -0
  44. data/lib/mongrel/http_response.rb +163 -0
  45. data/lib/mongrel/init.rb +10 -0
  46. data/lib/mongrel/logger.rb +74 -0
  47. data/lib/mongrel/mime_types.yml +616 -0
  48. data/lib/mongrel/rails.rb +185 -0
  49. data/lib/mongrel/stats.rb +89 -0
  50. data/lib/mongrel/tcphack.rb +18 -0
  51. data/lib/mongrel/uri_classifier.rb +76 -0
  52. data/mongrel-public_cert.pem +20 -0
  53. data/mongrel.gemspec +47 -0
  54. data/setup.rb +1585 -0
  55. data/test/mime.yaml +3 -0
  56. data/test/mongrel.conf +1 -0
  57. data/test/test_cgi_wrapper.rb +26 -0
  58. data/test/test_command.rb +86 -0
  59. data/test/test_conditional.rb +107 -0
  60. data/test/test_configurator.rb +88 -0
  61. data/test/test_debug.rb +25 -0
  62. data/test/test_handlers.rb +104 -0
  63. data/test/test_http11.rb +272 -0
  64. data/test/test_redirect_handler.rb +44 -0
  65. data/test/test_request_progress.rb +100 -0
  66. data/test/test_response.rb +127 -0
  67. data/test/test_stats.rb +35 -0
  68. data/test/test_uriclassifier.rb +261 -0
  69. data/test/test_ws.rb +116 -0
  70. data/test/testhelp.rb +74 -0
  71. data/tools/trickletest.rb +45 -0
  72. metadata +202 -0
@@ -0,0 +1,182 @@
1
+ # Copyright (c) 2005 Zed A. Shaw
2
+ # You can redistribute it and/or modify it under the same terms as Ruby.
3
+ #
4
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
5
+ # for more information.
6
+
7
+ require 'cgi'
8
+
9
+ module Mongrel
10
+ # The beginning of a complete wrapper around Mongrel's internal HTTP processing
11
+ # system but maintaining the original Ruby CGI module. Use this only as a crutch
12
+ # to get existing CGI based systems working. It should handle everything, but please
13
+ # notify me if you see special warnings. This work is still very alpha so I need
14
+ # testers to help work out the various corner cases.
15
+ #
16
+ # The CGIWrapper.handler attribute is normally not set and is available for
17
+ # frameworks that need to get back to the handler. Rails uses this to give
18
+ # people access to the RailsHandler#files (DirHandler really) so they can
19
+ # look-up paths and do other things with the files managed there.
20
+ #
21
+ # In Rails you can get the real file for a request with:
22
+ #
23
+ # path = @request.cgi.handler.files.can_serve(@request['PATH_INFO'])
24
+ #
25
+ # Which is ugly but does the job. Feel free to write a Rails helper for that.
26
+ # Refer to DirHandler#can_serve for more information on this.
27
+ class CGIWrapper < ::CGI
28
+ public :env_table
29
+ attr_reader :options
30
+ attr_accessor :handler
31
+ # Set this to false if you want calls to CGIWrapper.out to not actually send
32
+ # the response until you force it.
33
+ attr_accessor :default_really_final
34
+
35
+ # these are stripped out of any keys passed to CGIWrapper.header function
36
+ REMOVED_KEYS = [ "nph","status","server","connection","type",
37
+ "charset","length","language","expires"]
38
+
39
+ # Takes an HttpRequest and HttpResponse object, plus any additional arguments
40
+ # normally passed to CGI. These are used internally to create a wrapper around
41
+ # the real CGI while maintaining Mongrel's view of the world.
42
+ def initialize(request, response, *args)
43
+ @request = request
44
+ @response = response
45
+ @args = *args
46
+ @input = request.body
47
+ @head = {}
48
+ @out_called = false
49
+ @default_really_final=true
50
+ super(*args)
51
+ end
52
+
53
+ # The header is typically called to send back the header. In our case we
54
+ # collect it into a hash for later usage.
55
+ #
56
+ # nph -- Mostly ignored. It'll output the date.
57
+ # connection -- Completely ignored. Why is CGI doing this?
58
+ # length -- Ignored since Mongrel figures this out from what you write to output.
59
+ #
60
+ def header(options = "text/html")
61
+ # if they pass in a string then just write the Content-Type
62
+ if options.class == String
63
+ @head['Content-Type'] = options unless @head['Content-Type']
64
+ else
65
+ # convert the given options into what Mongrel wants
66
+ @head['Content-Type'] = options['type'] || "text/html"
67
+ @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
68
+
69
+ # setup date only if they use nph
70
+ @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
71
+
72
+ # setup the server to use the default or what they set
73
+ @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
74
+
75
+ # remaining possible options they can give
76
+ @head['Status'] = options['status'] if options['status']
77
+ @head['Content-Language'] = options['language'] if options['language']
78
+ @head['Expires'] = options['expires'] if options['expires']
79
+
80
+ # drop the keys we don't want anymore
81
+ REMOVED_KEYS.each {|k| options.delete(k) }
82
+
83
+ # finally just convert the rest raw (which puts 'cookie' directly)
84
+ # 'cookie' is translated later as we write the header out
85
+ options.each{|k,v| @head[k] = v}
86
+ end
87
+
88
+ # doing this fakes out the cgi library to think the headers are empty
89
+ # we then do the real headers in the out function call later
90
+ ""
91
+ end
92
+
93
+ # Takes any 'cookie' setting and sends it over the Mongrel header,
94
+ # then removes the setting from the options. If cookie is an
95
+ # Array or Hash then it sends those on with .to_s, otherwise
96
+ # it just calls .to_s on it and hopefully your "cookie" can
97
+ # write itself correctly.
98
+ def send_cookies(to)
99
+ # convert the cookies based on the myriad of possible ways to set a cookie
100
+ if @head['cookie']
101
+ cookie = @head['cookie']
102
+ case cookie
103
+ when Array
104
+ cookie.each {|c| to['Set-Cookie'] = c.to_s }
105
+ when Hash
106
+ cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
107
+ else
108
+ to['Set-Cookie'] = options['cookie'].to_s
109
+ end
110
+
111
+ @head.delete('cookie')
112
+ end
113
+
114
+ # @output_cookies seems to never be used, but we'll process it just in case
115
+ @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
116
+ end
117
+
118
+ # The dumb thing is people can call header or this or both and in any order.
119
+ # So, we just reuse header and then finalize the HttpResponse the right way.
120
+ # Status is taken from the various options and converted to what Mongrel needs
121
+ # via the CGIWrapper.status function.
122
+ #
123
+ # We also prevent Rails from actually doing the final send by adding a
124
+ # second parameter "really_final". Only Mongrel calls this after Rails
125
+ # is done. Since this will break other frameworks, it defaults to
126
+ # a different setting for rails (false) and (true) for others.
127
+ def out(options = "text/html", really_final=@default_really_final)
128
+ if @out_called || !really_final
129
+ # don't do it more than once or if it's not the really final call
130
+ return
131
+ end
132
+
133
+ header(options)
134
+
135
+ @response.start status do |head, body|
136
+ send_cookies(head)
137
+
138
+ @head.each {|k,v| head[k] = v}
139
+ body.write(yield || "")
140
+ end
141
+
142
+ @out_called = true
143
+ end
144
+
145
+ # Computes the status once, but lazily so that people who call header twice
146
+ # don't get penalized. Because CGI insists on including the options status
147
+ # message in the status we have to do a bit of parsing.
148
+ def status
149
+ if not @status
150
+ stat = @head["Status"]
151
+ stat = stat.split(' ')[0] if stat
152
+
153
+ @status = stat || "200"
154
+ end
155
+
156
+ @status
157
+ end
158
+
159
+ # Used to wrap the normal args variable used inside CGI.
160
+ def args
161
+ @args
162
+ end
163
+
164
+ # Used to wrap the normal env_table variable used inside CGI.
165
+ def env_table
166
+ @request.params
167
+ end
168
+
169
+ # Used to wrap the normal stdinput variable used inside CGI.
170
+ def stdinput
171
+ @input
172
+ end
173
+
174
+ # The stdoutput should be completely bypassed but we'll drop a warning just in case
175
+ def stdoutput
176
+ STDERR.puts "#{Time.now.httpdate}: WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks."
177
+ @response.body
178
+ end
179
+
180
+ end
181
+
182
+ end
@@ -0,0 +1,220 @@
1
+ # Copyright (c) 2005 Zed A. Shaw
2
+ # You can redistribute it and/or modify it under the same terms as Ruby.
3
+ #
4
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
5
+ # for more information.
6
+
7
+ require 'singleton'
8
+ require 'optparse'
9
+
10
+ require 'mongrel/gems'
11
+ Mongrel::Gems.require 'gem_plugin'
12
+
13
+ module Mongrel
14
+
15
+ # Contains all of the various commands that are used with
16
+ # Mongrel servers.
17
+
18
+ module Command
19
+
20
+ BANNER = "Usage: mongrel_rails <command> [options]"
21
+
22
+ # A Command pattern implementation used to create the set of command available to the user
23
+ # from Mongrel. The script uses objects which implement this interface to do the
24
+ # user's bidding.
25
+ module Base
26
+
27
+ attr_reader :valid, :done_validating, :original_args
28
+
29
+ # Called by the implemented command to set the options for that command.
30
+ # Every option has a short and long version, a description, a variable to
31
+ # set, and a default value. No exceptions.
32
+ def options(opts)
33
+ # process the given options array
34
+ opts.each do |short, long, help, variable, default|
35
+ self.instance_variable_set(variable, default)
36
+ @opt.on(short, long, help) do |arg|
37
+ self.instance_variable_set(variable, arg)
38
+ end
39
+ end
40
+ end
41
+
42
+ # Called by the subclass to setup the command and parse the argv arguments.
43
+ # The call is destructive on argv since it uses the OptionParser#parse! function.
44
+ def initialize(options={})
45
+ argv = options[:argv] || []
46
+ @opt = OptionParser.new
47
+ @opt.banner = Mongrel::Command::BANNER
48
+ @valid = true
49
+ # this is retarded, but it has to be done this way because -h and -v exit
50
+ @done_validating = false
51
+ @original_args = argv.dup
52
+
53
+ configure
54
+
55
+ # I need to add my own -h definition to prevent the -h by default from exiting.
56
+ @opt.on_tail("-h", "--help", "Show this message") do
57
+ @done_validating = true
58
+ Mongrel.log(@opt)
59
+ end
60
+
61
+ # I need to add my own -v definition to prevent the -v from exiting by default as well.
62
+ @opt.on_tail("--version", "Show version") do
63
+ @done_validating = true
64
+ Mongrel.log("Version #{Mongrel::Const::MONGREL_VERSION}")
65
+ end
66
+
67
+ @opt.parse! argv
68
+ end
69
+
70
+ def configure
71
+ options []
72
+ end
73
+
74
+ # Returns true/false depending on whether the command is configured properly.
75
+ def validate
76
+ return @valid
77
+ end
78
+
79
+ # Returns a help message. Defaults to OptionParser#help which should be good.
80
+ def help
81
+ @opt.help
82
+ end
83
+
84
+ # Runs the command doing it's job. You should implement this otherwise it will
85
+ # throw a NotImplementedError as a reminder.
86
+ def run
87
+ raise NotImplementedError
88
+ end
89
+
90
+
91
+ # Validates the given expression is true and prints the message if not, exiting.
92
+ def valid?(exp, message)
93
+ if not @done_validating and (not exp)
94
+ failure message
95
+ @valid = false
96
+ @done_validating = true
97
+ end
98
+ end
99
+
100
+ # Validates that a file exists and if not displays the message
101
+ def valid_exists?(file, message)
102
+ valid?(file != nil && File.exist?(file), message)
103
+ end
104
+
105
+
106
+ # Validates that the file is a file and not a directory or something else.
107
+ def valid_file?(file, message)
108
+ valid?(file != nil && File.file?(file), message)
109
+ end
110
+
111
+ # Validates that the given directory exists
112
+ def valid_dir?(file, message)
113
+ valid?(file != nil && File.directory?(file), message)
114
+ end
115
+
116
+ def valid_user?(user)
117
+ valid?(@group, "You must also specify a group.")
118
+ begin
119
+ Etc.getpwnam(user)
120
+ rescue
121
+ failure "User does not exist: #{user}"
122
+ @valid = false
123
+ end
124
+ end
125
+
126
+ def valid_group?(group)
127
+ valid?(@user, "You must also specify a user.")
128
+ begin
129
+ Etc.getgrnam(group)
130
+ rescue
131
+ failure "Group does not exist: #{group}"
132
+ @valid = false
133
+ end
134
+ end
135
+
136
+ # Just a simple method to display failure until something better is developed.
137
+ def failure(message)
138
+ STDERR.puts "!!! #{message}"
139
+ end
140
+ end
141
+
142
+ # A Singleton class that manages all of the available commands
143
+ # and handles running them.
144
+ class Registry
145
+ include Singleton
146
+
147
+ # Builds a list of possible commands from the Command derivates list
148
+ def commands
149
+ pmgr = GemPlugin::Manager.instance
150
+ list = pmgr.plugins["/commands"].keys
151
+ return list.sort
152
+ end
153
+
154
+ # Prints a list of available commands.
155
+ def print_command_list
156
+ Mongrel.log("#{Mongrel::Command::BANNER}\nAvailable commands are:\n\n")
157
+
158
+ self.commands.each do |name|
159
+ if /mongrel::/ =~ name
160
+ name = name[9 .. -1]
161
+ end
162
+
163
+ Mongrel.log(" - #{name[1 .. -1]}\n")
164
+ end
165
+
166
+ Mongrel.log("\nEach command takes -h as an option to get help.")
167
+
168
+ end
169
+
170
+
171
+ # Runs the args against the first argument as the command name.
172
+ # If it has any errors it returns a false, otherwise it return true.
173
+ def run(args)
174
+ # find the command
175
+ cmd_name = args.shift
176
+
177
+ if !cmd_name or cmd_name == "?" or cmd_name == "help"
178
+ print_command_list
179
+ return true
180
+ elsif cmd_name == "--version"
181
+ Mongrel.log("Mongrel Web Server #{Mongrel::Const::MONGREL_VERSION}")
182
+ return true
183
+ end
184
+
185
+ begin
186
+ # quick hack so that existing commands will keep working but the Mongrel:: ones can be moved
187
+ if ["start", "stop", "restart"].include? cmd_name
188
+ cmd_name = "mongrel::" + cmd_name
189
+ end
190
+
191
+ command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", :argv => args)
192
+ rescue OptionParser::InvalidOption
193
+ STDERR.puts "#$! for command '#{cmd_name}'"
194
+ STDERR.puts "Try #{cmd_name} -h to get help."
195
+ return false
196
+ rescue
197
+ STDERR.puts "ERROR RUNNING '#{cmd_name}': #$!"
198
+ STDERR.puts "Use help command to get help"
199
+ return false
200
+ end
201
+
202
+ # Normally the command is NOT valid right after being created
203
+ # but sometimes (like with -h or -v) there's no further processing
204
+ # needed so the command is already valid so we can skip it.
205
+ if not command.done_validating
206
+ if not command.validate
207
+ STDERR.puts "#{cmd_name} reported an error. Use mongrel_rails #{cmd_name} -h to get help."
208
+ return false
209
+ else
210
+ command.run
211
+ end
212
+ end
213
+
214
+ return true
215
+ end
216
+
217
+ end
218
+ end
219
+ end
220
+
@@ -0,0 +1,389 @@
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