headius-mongrel 1.1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +21 -0
- data/COPYING +55 -0
- data/LICENSE +55 -0
- data/Manifest +69 -0
- data/README +74 -0
- data/Rakefile +202 -0
- data/TODO +5 -0
- data/bin/mongrel_rails +283 -0
- data/examples/builder.rb +29 -0
- data/examples/camping/README +3 -0
- data/examples/camping/blog.rb +294 -0
- data/examples/camping/tepee.rb +149 -0
- data/examples/httpd.conf +474 -0
- data/examples/mime.yaml +3 -0
- data/examples/mongrel.conf +9 -0
- data/examples/mongrel_simple_ctrl.rb +92 -0
- data/examples/mongrel_simple_service.rb +116 -0
- data/examples/monitrc +57 -0
- data/examples/random_thrash.rb +19 -0
- data/examples/simpletest.rb +52 -0
- data/examples/webrick_compare.rb +20 -0
- data/ext/http11/ext_help.h +14 -0
- data/ext/http11/extconf.rb +6 -0
- data/ext/http11/http11.c +402 -0
- data/ext/http11/http11_parser.c +1221 -0
- data/ext/http11/http11_parser.h +49 -0
- data/ext/http11/http11_parser.java.rl +170 -0
- data/ext/http11/http11_parser.rl +152 -0
- data/ext/http11/http11_parser_common.rl +54 -0
- data/ext/http11_java/Http11Service.java +13 -0
- data/ext/http11_java/org/jruby/mongrel/Http11.java +353 -0
- data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
- data/lib/mongrel.rb +364 -0
- data/lib/mongrel/camping.rb +107 -0
- data/lib/mongrel/cgi.rb +181 -0
- data/lib/mongrel/command.rb +222 -0
- data/lib/mongrel/configurator.rb +388 -0
- data/lib/mongrel/const.rb +110 -0
- data/lib/mongrel/debug.rb +203 -0
- data/lib/mongrel/gems.rb +22 -0
- data/lib/mongrel/handlers.rb +468 -0
- data/lib/mongrel/header_out.rb +28 -0
- data/lib/mongrel/http_request.rb +155 -0
- data/lib/mongrel/http_response.rb +163 -0
- data/lib/mongrel/init.rb +10 -0
- data/lib/mongrel/mime_types.yml +616 -0
- data/lib/mongrel/rails.rb +192 -0
- data/lib/mongrel/stats.rb +89 -0
- data/lib/mongrel/tcphack.rb +18 -0
- data/lib/mongrel/uri_classifier.rb +76 -0
- data/mongrel-public_cert.pem +20 -0
- data/mongrel.gemspec +47 -0
- data/setup.rb +1585 -0
- data/test/mime.yaml +3 -0
- data/test/mongrel.conf +1 -0
- data/test/test_cgi_wrapper.rb +26 -0
- data/test/test_command.rb +86 -0
- data/test/test_conditional.rb +107 -0
- data/test/test_configurator.rb +88 -0
- data/test/test_debug.rb +25 -0
- data/test/test_handlers.rb +126 -0
- data/test/test_http11.rb +156 -0
- data/test/test_redirect_handler.rb +45 -0
- data/test/test_request_progress.rb +100 -0
- data/test/test_response.rb +127 -0
- data/test/test_stats.rb +35 -0
- data/test/test_uriclassifier.rb +261 -0
- data/test/test_ws.rb +115 -0
- data/test/testhelp.rb +79 -0
- data/tools/trickletest.rb +45 -0
- metadata +199 -0
data/lib/mongrel/cgi.rb
ADDED
@@ -0,0 +1,181 @@
|
|
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 "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
|
+
end
|
@@ -0,0 +1,222 @@
|
|
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
|
+
puts @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
|
+
if VERSION
|
65
|
+
puts "Version #{Mongrel::Const::MONGREL_VERSION}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@opt.parse! argv
|
70
|
+
end
|
71
|
+
|
72
|
+
def configure
|
73
|
+
options []
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true/false depending on whether the command is configured properly.
|
77
|
+
def validate
|
78
|
+
return @valid
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns a help message. Defaults to OptionParser#help which should be good.
|
82
|
+
def help
|
83
|
+
@opt.help
|
84
|
+
end
|
85
|
+
|
86
|
+
# Runs the command doing it's job. You should implement this otherwise it will
|
87
|
+
# throw a NotImplementedError as a reminder.
|
88
|
+
def run
|
89
|
+
raise NotImplementedError
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Validates the given expression is true and prints the message if not, exiting.
|
94
|
+
def valid?(exp, message)
|
95
|
+
if not @done_validating and (not exp)
|
96
|
+
failure message
|
97
|
+
@valid = false
|
98
|
+
@done_validating = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Validates that a file exists and if not displays the message
|
103
|
+
def valid_exists?(file, message)
|
104
|
+
valid?(file != nil && File.exist?(file), message)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Validates that the file is a file and not a directory or something else.
|
109
|
+
def valid_file?(file, message)
|
110
|
+
valid?(file != nil && File.file?(file), message)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Validates that the given directory exists
|
114
|
+
def valid_dir?(file, message)
|
115
|
+
valid?(file != nil && File.directory?(file), message)
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_user?(user)
|
119
|
+
valid?(@group, "You must also specify a group.")
|
120
|
+
begin
|
121
|
+
Etc.getpwnam(user)
|
122
|
+
rescue
|
123
|
+
failure "User does not exist: #{user}"
|
124
|
+
@valid = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def valid_group?(group)
|
129
|
+
valid?(@user, "You must also specify a user.")
|
130
|
+
begin
|
131
|
+
Etc.getgrnam(group)
|
132
|
+
rescue
|
133
|
+
failure "Group does not exist: #{group}"
|
134
|
+
@valid = false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Just a simple method to display failure until something better is developed.
|
139
|
+
def failure(message)
|
140
|
+
STDERR.puts "!!! #{message}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# A Singleton class that manages all of the available commands
|
145
|
+
# and handles running them.
|
146
|
+
class Registry
|
147
|
+
include Singleton
|
148
|
+
|
149
|
+
# Builds a list of possible commands from the Command derivates list
|
150
|
+
def commands
|
151
|
+
pmgr = GemPlugin::Manager.instance
|
152
|
+
list = pmgr.plugins["/commands"].keys
|
153
|
+
return list.sort
|
154
|
+
end
|
155
|
+
|
156
|
+
# Prints a list of available commands.
|
157
|
+
def print_command_list
|
158
|
+
puts "#{Mongrel::Command::BANNER}\nAvailable commands are:\n\n"
|
159
|
+
|
160
|
+
self.commands.each do |name|
|
161
|
+
if /mongrel::/ =~ name
|
162
|
+
name = name[9 .. -1]
|
163
|
+
end
|
164
|
+
|
165
|
+
puts " - #{name[1 .. -1]}\n"
|
166
|
+
end
|
167
|
+
|
168
|
+
puts "\nEach command takes -h as an option to get help."
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# Runs the args against the first argument as the command name.
|
174
|
+
# If it has any errors it returns a false, otherwise it return true.
|
175
|
+
def run(args)
|
176
|
+
# find the command
|
177
|
+
cmd_name = args.shift
|
178
|
+
|
179
|
+
if !cmd_name or cmd_name == "?" or cmd_name == "help"
|
180
|
+
print_command_list
|
181
|
+
return true
|
182
|
+
elsif cmd_name == "--version"
|
183
|
+
puts "Mongrel Web Server #{Mongrel::Const::MONGREL_VERSION}"
|
184
|
+
return true
|
185
|
+
end
|
186
|
+
|
187
|
+
begin
|
188
|
+
# quick hack so that existing commands will keep working but the Mongrel:: ones can be moved
|
189
|
+
if ["start", "stop", "restart"].include? cmd_name
|
190
|
+
cmd_name = "mongrel::" + cmd_name
|
191
|
+
end
|
192
|
+
|
193
|
+
command = GemPlugin::Manager.instance.create("/commands/#{cmd_name}", :argv => args)
|
194
|
+
rescue OptionParser::InvalidOption
|
195
|
+
STDERR.puts "#$! for command '#{cmd_name}'"
|
196
|
+
STDERR.puts "Try #{cmd_name} -h to get help."
|
197
|
+
return false
|
198
|
+
rescue
|
199
|
+
STDERR.puts "ERROR RUNNING '#{cmd_name}': #$!"
|
200
|
+
STDERR.puts "Use help command to get help"
|
201
|
+
return false
|
202
|
+
end
|
203
|
+
|
204
|
+
# Normally the command is NOT valid right after being created
|
205
|
+
# but sometimes (like with -h or -v) there's no further processing
|
206
|
+
# needed so the command is already valid so we can skip it.
|
207
|
+
if not command.done_validating
|
208
|
+
if not command.validate
|
209
|
+
STDERR.puts "#{cmd_name} reported an error. Use mongrel_rails #{cmd_name} -h to get help."
|
210
|
+
return false
|
211
|
+
else
|
212
|
+
command.run
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
return true
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
@@ -0,0 +1,388 @@
|
|
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
|
+
log "Initiating groups for #{user.inspect}:#{group.inspect}."
|
63
|
+
Process.initgroups(user, target_gid)
|
64
|
+
|
65
|
+
log "Changing group to #{group.inspect}."
|
66
|
+
Process::GID.change_privilege(target_gid)
|
67
|
+
|
68
|
+
log "Changing user to #{user.inspect}."
|
69
|
+
Process::UID.change_privilege(target_uid)
|
70
|
+
end
|
71
|
+
rescue Errno::EPERM => e
|
72
|
+
log "Couldn't change user and group to #{user.inspect}:#{group.inspect}: #{e.to_s}."
|
73
|
+
log "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
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
85
|
+
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
|
+
ops = resolve_defaults(options)
|
135
|
+
ops[:num_processors] ||= 950
|
136
|
+
ops[:throttle] ||= 0
|
137
|
+
ops[:timeout] ||= 60
|
138
|
+
|
139
|
+
@listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i)
|
140
|
+
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
141
|
+
@listeners[@listener_name] = @listener
|
142
|
+
|
143
|
+
if ops[:user] and ops[:group]
|
144
|
+
change_privilege(ops[:user], ops[:group])
|
145
|
+
end
|
146
|
+
|
147
|
+
# Does the actual cloaking operation to give the new implicit self.
|
148
|
+
if block
|
149
|
+
cloaker(&block).bind(self).call
|
150
|
+
end
|
151
|
+
|
152
|
+
# all done processing this listener setup, reset implicit variables
|
153
|
+
@listener = nil
|
154
|
+
@listener_name = nil
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# Called inside a Configurator.listener block in order to
|
159
|
+
# add URI->handler mappings for that listener. Use this as
|
160
|
+
# many times as you like. It expects the following options
|
161
|
+
# or defaults:
|
162
|
+
#
|
163
|
+
# * :handler => HttpHandler -- Handler to use for this location.
|
164
|
+
# * :in_front => true/false -- Rather than appending, it prepends this handler.
|
165
|
+
def uri(location, options={})
|
166
|
+
ops = resolve_defaults(options)
|
167
|
+
@listener.register(location, ops[:handler], ops[:in_front])
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# Daemonizes the current Ruby script turning all the
|
172
|
+
# listeners into an actual "server" or detached process.
|
173
|
+
# You must call this *before* frameworks that open files
|
174
|
+
# as otherwise the files will be closed by this function.
|
175
|
+
#
|
176
|
+
# Does not work for Win32 systems (the call is silently ignored).
|
177
|
+
#
|
178
|
+
# Requires the following options or defaults:
|
179
|
+
#
|
180
|
+
# * :cwd => Directory to change to.
|
181
|
+
# * :log_file => Where to write STDOUT and STDERR.
|
182
|
+
#
|
183
|
+
# It is safe to call this on win32 as it will only require the daemons
|
184
|
+
# gem/library if NOT win32.
|
185
|
+
def daemonize(options={})
|
186
|
+
ops = resolve_defaults(options)
|
187
|
+
# save this for later since daemonize will hose it
|
188
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
189
|
+
require 'daemons/daemonize'
|
190
|
+
|
191
|
+
logfile = ops[:log_file]
|
192
|
+
if logfile[0].chr != "/"
|
193
|
+
logfile = File.join(ops[:cwd],logfile)
|
194
|
+
if not File.exist?(File.dirname(logfile))
|
195
|
+
log "!!! Log file directory not found at full path #{File.dirname(logfile)}. Update your configuration to use a full path."
|
196
|
+
exit 1
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
Daemonize.daemonize(logfile)
|
201
|
+
|
202
|
+
# change back to the original starting directory
|
203
|
+
Dir.chdir(ops[:cwd])
|
204
|
+
|
205
|
+
else
|
206
|
+
log "WARNING: Win32 does not support daemon mode."
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
# Uses the GemPlugin system to easily load plugins based on their
|
212
|
+
# gem dependencies. You pass in either an :includes => [] or
|
213
|
+
# :excludes => [] setting listing the names of plugins to include
|
214
|
+
# or exclude from the determining the dependencies.
|
215
|
+
def load_plugins(options={})
|
216
|
+
ops = resolve_defaults(options)
|
217
|
+
|
218
|
+
load_settings = {}
|
219
|
+
if ops[:includes]
|
220
|
+
ops[:includes].each do |plugin|
|
221
|
+
load_settings[plugin] = GemPlugin::INCLUDE
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
if ops[:excludes]
|
226
|
+
ops[:excludes].each do |plugin|
|
227
|
+
load_settings[plugin] = GemPlugin::EXCLUDE
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
GemPlugin::Manager.instance.load(load_settings)
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# Easy way to load a YAML file and apply default settings.
|
236
|
+
def load_yaml(file, default={})
|
237
|
+
default.merge(YAML.load_file(file))
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
# Loads the MIME map file and checks that it is correct
|
242
|
+
# on loading. This is commonly passed to Mongrel::DirHandler
|
243
|
+
# or any framework handler that uses DirHandler to serve files.
|
244
|
+
# You can also include a set of default MIME types as additional
|
245
|
+
# settings. See Mongrel::DirHandler for how the MIME types map
|
246
|
+
# is organized.
|
247
|
+
def load_mime_map(file, mime={})
|
248
|
+
# configure any requested mime map
|
249
|
+
mime = load_yaml(file, mime)
|
250
|
+
|
251
|
+
# check all the mime types to make sure they are the right format
|
252
|
+
mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
|
253
|
+
|
254
|
+
return mime
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
# Loads and creates a plugin for you based on the given
|
259
|
+
# name and configured with the selected options. The options
|
260
|
+
# are merged with the defaults prior to passing them in.
|
261
|
+
def plugin(name, options={})
|
262
|
+
ops = resolve_defaults(options)
|
263
|
+
GemPlugin::Manager.instance.create(name, ops)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Lets you do redirects easily as described in Mongrel::RedirectHandler.
|
267
|
+
# You use it inside the configurator like this:
|
268
|
+
#
|
269
|
+
# redirect("/test", "/to/there") # simple
|
270
|
+
# redirect("/to", /t/, 'w') # regexp
|
271
|
+
# redirect("/hey", /(w+)/) {|match| ...} # block
|
272
|
+
#
|
273
|
+
def redirect(from, pattern, replacement = nil, &block)
|
274
|
+
uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Works like a meta run method which goes through all the
|
278
|
+
# configured listeners. Use the Configurator.join method
|
279
|
+
# to prevent Ruby from exiting until each one is done.
|
280
|
+
def run
|
281
|
+
@listeners.each {|name,s|
|
282
|
+
s.run
|
283
|
+
}
|
284
|
+
|
285
|
+
$mongrel_sleeper_thread = Thread.new { loop { sleep 1 } }
|
286
|
+
end
|
287
|
+
|
288
|
+
# Calls .stop on all the configured listeners so they
|
289
|
+
# stop processing requests (gracefully). By default it
|
290
|
+
# assumes that you don't want to restart.
|
291
|
+
def stop(needs_restart=false, synchronous=false)
|
292
|
+
@listeners.each do |name,s|
|
293
|
+
s.stop(synchronous)
|
294
|
+
end
|
295
|
+
@needs_restart = needs_restart
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
# This method should actually be called *outside* of the
|
300
|
+
# Configurator block so that you can control it. In other words
|
301
|
+
# do it like: config.join.
|
302
|
+
def join
|
303
|
+
@listeners.values.each {|s| s.acceptor.join }
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
# Calling this before you register your URIs to the given location
|
308
|
+
# will setup a set of handlers that log open files, objects, and the
|
309
|
+
# parameters for each request. This helps you track common problems
|
310
|
+
# found in Rails applications that are either slow or become unresponsive
|
311
|
+
# after a little while.
|
312
|
+
#
|
313
|
+
# You can pass an extra parameter *what* to indicate what you want to
|
314
|
+
# debug. For example, if you just want to dump rails stuff then do:
|
315
|
+
#
|
316
|
+
# debug "/", what = [:rails]
|
317
|
+
#
|
318
|
+
# And it will only produce the log/mongrel_debug/rails.log file.
|
319
|
+
# Available options are: :access, :files, :objects, :threads, :rails
|
320
|
+
#
|
321
|
+
# NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick.
|
322
|
+
def debug(location, what = [:access, :files, :objects, :threads, :rails])
|
323
|
+
require 'mongrel/debug'
|
324
|
+
handlers = {
|
325
|
+
:access => "/handlers/requestlog::access",
|
326
|
+
:files => "/handlers/requestlog::files",
|
327
|
+
:objects => "/handlers/requestlog::objects",
|
328
|
+
:threads => "/handlers/requestlog::threads",
|
329
|
+
:rails => "/handlers/requestlog::params"
|
330
|
+
}
|
331
|
+
|
332
|
+
# turn on the debugging infrastructure, and ObjectTracker is a pig
|
333
|
+
MongrelDbg.configure
|
334
|
+
|
335
|
+
# now we roll through each requested debug type, turn it on and load that plugin
|
336
|
+
what.each do |type|
|
337
|
+
MongrelDbg.begin_trace type
|
338
|
+
uri location, :handler => plugin(handlers[type])
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Used to allow you to let users specify their own configurations
|
343
|
+
# inside your Configurator setup. You pass it a script name and
|
344
|
+
# it reads it in and does an eval on the contents passing in the right
|
345
|
+
# binding so they can put their own Configurator statements.
|
346
|
+
def run_config(script)
|
347
|
+
open(script) {|f| eval(f.read, proc {self}) }
|
348
|
+
end
|
349
|
+
|
350
|
+
# Sets up the standard signal handlers that are used on most Ruby
|
351
|
+
# It only configures if the platform is not win32 and doesn't do
|
352
|
+
# a HUP signal since this is typically framework specific.
|
353
|
+
#
|
354
|
+
# Requires a :pid_file option given to Configurator.new to indicate a file to delete.
|
355
|
+
# It sets the MongrelConfig.needs_restart attribute if
|
356
|
+
# the start command should reload. It's up to you to detect this
|
357
|
+
# and do whatever is needed for a "restart".
|
358
|
+
#
|
359
|
+
# This command is safely ignored if the platform is win32 (with a warning)
|
360
|
+
def setup_signals(options={})
|
361
|
+
ops = resolve_defaults(options)
|
362
|
+
|
363
|
+
# forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C)
|
364
|
+
trap("INT") { log "INT signal received."; stop(false) }
|
365
|
+
|
366
|
+
# clean up the pid file always
|
367
|
+
at_exit { remove_pid_file }
|
368
|
+
|
369
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
370
|
+
# graceful shutdown
|
371
|
+
trap("TERM") { log "TERM signal received."; stop }
|
372
|
+
trap("USR1") { log "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"; $mongrel_debug_client = !$mongrel_debug_client }
|
373
|
+
# restart
|
374
|
+
trap("USR2") { log "USR2 signal received."; stop(true) }
|
375
|
+
|
376
|
+
log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)."
|
377
|
+
else
|
378
|
+
log "Signals ready. INT => stop (no restart)."
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Logs a simple message to STDERR (or the mongrel log if in daemon mode).
|
383
|
+
def log(msg)
|
384
|
+
STDERR.print "** ", msg, "\n"
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
end
|