freels-mongrel 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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