mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Backport of HttpRequest enhancements from 0.4. Patch to specify --prefix from Scott Laird. Fix for ids not working with GC.
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@292 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
parent
a24136cd55
commit
ab3c8082de
7 changed files with 488 additions and 437 deletions
2
Rakefile
2
Rakefile
|
@ -53,7 +53,7 @@ task :site => [:site_webgen, :site_rdoc, :site_coverage, :site_projects_rdoc]
|
|||
setup_extension("http11", "http11")
|
||||
|
||||
name="mongrel"
|
||||
version="0.3.13.3"
|
||||
version="0.3.13.4"
|
||||
|
||||
setup_gem(name, version) do |spec|
|
||||
spec.summary = "A small fast HTTP library and server that runs Rails, Camping, and Nitro apps."
|
||||
|
|
|
@ -32,7 +32,8 @@ class Start < GemPlugin::Plugin "/commands"
|
|||
['-S', '--script PATH', "Load the given file as an extra config script.", :@config_script, nil],
|
||||
['-G', '--generate CONFIG', "Generate a config file for -C", :@generate, nil],
|
||||
['', '--user USER', "User to run as", :@user, nil],
|
||||
['', '--group GROUP', "Group to run as", :@group, nil]
|
||||
['', '--group GROUP', "Group to run as", :@group, nil],
|
||||
['', '--prefix PATH', "URL prefix for Rails app", :@prefix, '/']
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -64,7 +65,7 @@ class Start < GemPlugin::Plugin "/commands"
|
|||
:docroot => @docroot, :mime_map => @mime_map, :daemon => @daemon,
|
||||
:debug => @debug, :includes => ["mongrel"], :config_script => @config_script,
|
||||
:num_processors => @num_procs, :timeout => @timeout,
|
||||
:user => @user, :group => @group
|
||||
:user => @user, :group => @group, :prefix => @prefix
|
||||
}
|
||||
|
||||
if @generate
|
||||
|
@ -103,13 +104,10 @@ class Start < GemPlugin::Plugin "/commands"
|
|||
if defaults[:debug]
|
||||
log "Installing debugging prefixed filters. Look in log/mongrel_debug for the files."
|
||||
debug "/"
|
||||
elsif not defaults[:daemon]
|
||||
# they don't have debug on and aren't in daemon so at least log accesses
|
||||
debug "/", what = [:files]
|
||||
end
|
||||
|
||||
log "Starting Rails with #{defaults[:environment]} environment ..."
|
||||
uri "/", :handler => rails(:mime => mime)
|
||||
log "Starting Rails with #{defaults[:environment]} environment on #{defaults[:prefix]} ..."
|
||||
uri defaults[:prefix], :handler => rails(:mime => mime, :prefix => @prefix)
|
||||
log "Rails loaded."
|
||||
|
||||
log "Loading any Rails specific GemPlugins"
|
||||
|
|
|
@ -14,7 +14,9 @@ static VALUE mMongrel;
|
|||
static VALUE cHttpParser;
|
||||
static VALUE cURIClassifier;
|
||||
static VALUE eHttpParserError;
|
||||
static ID id_handler_map;
|
||||
|
||||
#define id_handler_map rb_intern("@handler_map")
|
||||
#define id_http_body rb_intern("@http_body")
|
||||
|
||||
static VALUE global_http_prefix;
|
||||
static VALUE global_request_method;
|
||||
|
@ -121,7 +123,7 @@ void http_version(void *data, const char *at, size_t length)
|
|||
}
|
||||
|
||||
/** Finalizes the request header to have a bunch of stuff that's
|
||||
needed. */
|
||||
needed. */
|
||||
|
||||
void header_done(void *data, const char *at, size_t length)
|
||||
{
|
||||
|
@ -148,44 +150,46 @@ void header_done(void *data, const char *at, size_t length)
|
|||
if(colon != NULL) {
|
||||
rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - RSTRING(temp)->ptr));
|
||||
rb_hash_aset(req, global_server_port,
|
||||
rb_str_substr(temp, colon - RSTRING(temp)->ptr+1,
|
||||
RSTRING(temp)->len));
|
||||
rb_str_substr(temp, colon - RSTRING(temp)->ptr+1,
|
||||
RSTRING(temp)->len));
|
||||
} else {
|
||||
rb_hash_aset(req, global_server_name, temp);
|
||||
rb_hash_aset(req, global_server_port, global_port_80);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// grab the initial body and stuff it into an ivar
|
||||
rb_ivar_set(req, id_http_body, rb_str_new(at, length));
|
||||
rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
|
||||
rb_hash_aset(req, global_server_software, global_mongrel_version);
|
||||
}
|
||||
|
||||
|
||||
void HttpParser_free(void *data) {
|
||||
TRACE();
|
||||
|
||||
if(data) {
|
||||
free(data);
|
||||
}
|
||||
TRACE();
|
||||
|
||||
if(data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VALUE HttpParser_alloc(VALUE klass)
|
||||
{
|
||||
VALUE obj;
|
||||
http_parser *hp = ALLOC_N(http_parser, 1);
|
||||
TRACE();
|
||||
hp->http_field = http_field;
|
||||
hp->request_method = request_method;
|
||||
hp->request_uri = request_uri;
|
||||
hp->query_string = query_string;
|
||||
hp->http_version = http_version;
|
||||
hp->header_done = header_done;
|
||||
http_parser_init(hp);
|
||||
VALUE obj;
|
||||
http_parser *hp = ALLOC_N(http_parser, 1);
|
||||
TRACE();
|
||||
hp->http_field = http_field;
|
||||
hp->request_method = request_method;
|
||||
hp->request_uri = request_uri;
|
||||
hp->query_string = query_string;
|
||||
hp->http_version = http_version;
|
||||
hp->header_done = header_done;
|
||||
http_parser_init(hp);
|
||||
|
||||
obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
|
||||
obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
@ -200,7 +204,7 @@ VALUE HttpParser_init(VALUE self)
|
|||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
http_parser_init(http);
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -217,7 +221,7 @@ VALUE HttpParser_reset(VALUE self)
|
|||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
http_parser_init(http);
|
||||
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
|
@ -234,7 +238,7 @@ VALUE HttpParser_finish(VALUE self)
|
|||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
http_parser_finish(http);
|
||||
|
||||
|
||||
return http_parser_is_finished(http) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
|
@ -268,15 +272,15 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|||
from = FIX2INT(start);
|
||||
dptr = RSTRING(data)->ptr;
|
||||
dlen = RSTRING(data)->len;
|
||||
|
||||
|
||||
if(from >= dlen) {
|
||||
rb_raise(eHttpParserError, "Requested start is after data buffer end.");
|
||||
} else {
|
||||
http->data = (void *)req_hash;
|
||||
http_parser_execute(http, dptr, dlen, from);
|
||||
|
||||
|
||||
VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
|
||||
|
||||
|
||||
if(http_parser_has_error(http)) {
|
||||
rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
||||
} else {
|
||||
|
@ -297,7 +301,7 @@ VALUE HttpParser_has_error(VALUE self)
|
|||
{
|
||||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
|
||||
|
||||
return http_parser_has_error(http) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
|
@ -312,7 +316,7 @@ VALUE HttpParser_is_finished(VALUE self)
|
|||
{
|
||||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
|
||||
|
||||
return http_parser_is_finished(http) ? Qtrue : Qfalse;
|
||||
}
|
||||
|
||||
|
@ -328,32 +332,32 @@ VALUE HttpParser_nread(VALUE self)
|
|||
{
|
||||
http_parser *http = NULL;
|
||||
DATA_GET(self, http_parser, http);
|
||||
|
||||
|
||||
return INT2FIX(http->nread);
|
||||
}
|
||||
|
||||
|
||||
void URIClassifier_free(void *data)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
if(data) {
|
||||
tst_cleanup((struct tst *)data);
|
||||
}
|
||||
TRACE();
|
||||
|
||||
if(data) {
|
||||
tst_cleanup((struct tst *)data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
VALUE URIClassifier_alloc(VALUE klass)
|
||||
{
|
||||
VALUE obj;
|
||||
struct tst *tst = tst_init(TRIE_INCREASE);
|
||||
TRACE();
|
||||
assert(tst && "failed to initialize trie structure");
|
||||
VALUE obj;
|
||||
struct tst *tst = tst_init(TRIE_INCREASE);
|
||||
TRACE();
|
||||
assert(tst && "failed to initialize trie structure");
|
||||
|
||||
obj = Data_Wrap_Struct(klass, NULL, URIClassifier_free, tst);
|
||||
obj = Data_Wrap_Struct(klass, NULL, URIClassifier_free, tst);
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -415,7 +419,7 @@ VALUE URIClassifier_register(VALUE self, VALUE uri, VALUE handler)
|
|||
} else if(rc == TST_NULL_KEY) {
|
||||
rb_raise(rb_eStandardError, "URI was empty");
|
||||
}
|
||||
|
||||
|
||||
rb_hash_aset(rb_ivar_get(self, id_handler_map), uri, handler);
|
||||
|
||||
return Qnil;
|
||||
|
@ -504,7 +508,7 @@ VALUE URIClassifier_resolve(VALUE self, VALUE uri)
|
|||
// matches a script so process like normal
|
||||
rb_ary_push(result, rb_str_substr(uri, pref_len, RSTRING(uri)->len));
|
||||
}
|
||||
|
||||
|
||||
rb_ary_push(result, (VALUE)handler);
|
||||
} else {
|
||||
// not found so push back nothing
|
||||
|
@ -521,7 +525,6 @@ void Init_http11()
|
|||
{
|
||||
|
||||
mMongrel = rb_define_module("Mongrel");
|
||||
id_handler_map = rb_intern("@handler_map");
|
||||
|
||||
DEF_GLOBAL(http_prefix, "HTTP_");
|
||||
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
||||
|
@ -539,7 +542,7 @@ void Init_http11()
|
|||
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
||||
DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
|
||||
DEF_GLOBAL(http_host, "HTTP_HOST");
|
||||
DEF_GLOBAL(mongrel_version, "Mongrel 0.3.13.3");
|
||||
DEF_GLOBAL(mongrel_version, "Mongrel 0.3.13.4");
|
||||
DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
|
||||
DEF_GLOBAL(port_80, "80");
|
||||
|
||||
|
@ -562,5 +565,5 @@ void Init_http11()
|
|||
rb_define_method(cURIClassifier, "unregister", URIClassifier_unregister, 1);
|
||||
rb_define_method(cURIClassifier, "resolve", URIClassifier_resolve, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
438
lib/mongrel.rb
438
lib/mongrel.rb
|
@ -14,6 +14,7 @@ require 'mongrel/handlers'
|
|||
require 'mongrel/command'
|
||||
require 'mongrel/tcphack'
|
||||
require 'yaml'
|
||||
require 'mongrel/configurator'
|
||||
require 'time'
|
||||
require 'rubygems'
|
||||
require 'etc'
|
||||
|
@ -33,7 +34,7 @@ module Mongrel
|
|||
|
||||
class URIClassifier
|
||||
attr_reader :handler_map
|
||||
|
||||
|
||||
# Returns the URIs that have been registered with this classifier so far.
|
||||
# The URIs returned should not be modified as this will cause a memory leak.
|
||||
# You can use this to inspect the contents of the URIClassifier.
|
||||
|
@ -120,13 +121,13 @@ module Mongrel
|
|||
# The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
|
||||
REQUEST_URI='REQUEST_URI'.freeze
|
||||
|
||||
MONGREL_VERSION="0.3.13.3".freeze
|
||||
MONGREL_VERSION="0.3.13.4".freeze
|
||||
|
||||
# TODO: this use of a base for tempfiles needs to be looked at for security problems
|
||||
MONGREL_TMP_BASE="mongrel".freeze
|
||||
|
||||
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
||||
ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze
|
||||
ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze
|
||||
|
||||
CONTENT_LENGTH="CONTENT_LENGTH".freeze
|
||||
|
||||
|
@ -163,6 +164,10 @@ module Mongrel
|
|||
REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
|
||||
end
|
||||
|
||||
# Basically a Hash with one extra parameter for the HTTP body, mostly used internally.
|
||||
class HttpParams < Hash
|
||||
attr_accessor :http_body
|
||||
end
|
||||
|
||||
# When a handler is found for a registered URI then this class is constructed
|
||||
# and passed to your HttpHandler::process method. You should assume that
|
||||
|
@ -186,27 +191,58 @@ module Mongrel
|
|||
# body data into the HttpRequest.body attribute.
|
||||
#
|
||||
# TODO: Implement tempfile removal when the request is done.
|
||||
def initialize(params, initial_body, socket, notifier)
|
||||
def initialize(params, socket, dispatcher)
|
||||
@params = params
|
||||
@socket = socket
|
||||
content_length = params[Const::CONTENT_LENGTH].to_i
|
||||
http_body_len = params.http_body.length
|
||||
|
||||
clen = params[Const::CONTENT_LENGTH].to_i - initial_body.length
|
||||
total = clen
|
||||
dispatcher.request_begins(params) if dispatcher
|
||||
|
||||
if clen > Const::MAX_BODY
|
||||
@body = Tempfile.new(Const::MONGREL_TMP_BASE)
|
||||
@body.binmode
|
||||
else
|
||||
# conditions to test:
|
||||
# * http_body_len == 0 && content_length == 0 -- Nothing to do
|
||||
# * http_body_len > content_length -- ERROR, abort
|
||||
# * http_body_len < content_length -- need to read more
|
||||
# * http_body_len == content_length -- initial body has all of it
|
||||
if http_body_len == 0 && content_length == 0
|
||||
# no body to process
|
||||
@body = StringIO.new
|
||||
dispatcher.request_progress(params, 0, 0) if dispatcher
|
||||
elsif http_body_len > content_length
|
||||
# ERROR, they're sending bad requests
|
||||
raise HttpParserError.new("Sent body size #{http_body_len} but declared Content-Length: #{content_length}")
|
||||
elsif http_body_len < content_length
|
||||
# must read more data to complete body
|
||||
clen = content_length - http_body_len
|
||||
if clen > Const::MAX_BODY
|
||||
# huge body, put it in a tempfile
|
||||
@body = Tempfile.new(Const::MONGREL_TMP_BASE)
|
||||
@body.binmode
|
||||
else
|
||||
# small body, just use that
|
||||
@body = StringIO.new(params.http_body)
|
||||
end
|
||||
read_body(clen, dispatcher)
|
||||
elsif http_body_len == content_length
|
||||
# we've got everything, pack it up
|
||||
@body = StringIO.new(params.http_body)
|
||||
dispatcher.request_progress(params, 0, http_body_len) if dispatcher
|
||||
else
|
||||
STDERR.puts "BAD LOGIC: Tell Zed he's a moron."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Does the heavy lifting of properly reading the larger body requests in
|
||||
# small chunks. It expects @body to be an IO object, @socket to be valid,
|
||||
# and will set @body = nil if the request fails. It also expects any initial
|
||||
# part of the body that has been read to be in the @body already.
|
||||
def read_body(clen, dispatcher)
|
||||
begin
|
||||
@body.write(initial_body)
|
||||
notifier.request_begins(params) if notifier
|
||||
|
||||
total = clen
|
||||
# write the odd sized chunk first
|
||||
clen -= @body.write(@socket.read(clen % Const::CHUNK_SIZE))
|
||||
notifier.request_progress(params, clen, total) if notifier
|
||||
dispatcher.request_progress(params, clen, total) if dispatcher
|
||||
|
||||
# then stream out nothing but perfectly sized chunks
|
||||
while clen > 0 and !@socket.closed?
|
||||
|
@ -215,7 +251,7 @@ module Mongrel
|
|||
raise "Socket closed or read failure" if not data or data.length != Const::CHUNK_SIZE
|
||||
clen -= @body.write(data)
|
||||
# ASSUME: we are writing to a disk and these writes always write the requested amount
|
||||
notifier.request_progress(params, clen, total) if notifier
|
||||
dispatcher.request_progress(params, clen, total) if dispatcher
|
||||
end
|
||||
|
||||
# rewind to keep the world happy
|
||||
|
@ -228,6 +264,7 @@ module Mongrel
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
# Performs URI escaping so that you can construct proper
|
||||
# query strings faster. Use this rather than the cgi.rb
|
||||
# version since it's faster. (Stolen from Camping).
|
||||
|
@ -514,7 +551,7 @@ module Mongrel
|
|||
def process_client(client)
|
||||
begin
|
||||
parser = HttpParser.new
|
||||
params = {}
|
||||
params = HttpParams.new
|
||||
request = nil
|
||||
data = client.readpartial(Const::CHUNK_SIZE)
|
||||
nparsed = 0
|
||||
|
@ -535,10 +572,7 @@ module Mongrel
|
|||
params[Const::REMOTE_ADDR] = params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last
|
||||
notifier = handlers[0].request_notify ? handlers[0] : nil
|
||||
|
||||
# TODO: Find a faster/better way to carve out the range, preferably without copying.
|
||||
data = data[nparsed ... data.length] || ""
|
||||
|
||||
request = HttpRequest.new(params, data, client, notifier)
|
||||
request = HttpRequest.new(params, client, notifier)
|
||||
|
||||
# in the case of large file uploads the user could close the socket, so skip those requests
|
||||
break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted
|
||||
|
@ -578,6 +612,7 @@ module Mongrel
|
|||
reap_dead_workers('too many files')
|
||||
rescue Object
|
||||
STDERR.puts "#{Time.now}: ERROR: #$!"
|
||||
STDERR.puts $!.backtrace.join("\n")
|
||||
ensure
|
||||
client.close unless client.closed?
|
||||
request.body.delete if request and request.body.class == Tempfile
|
||||
|
@ -646,6 +681,9 @@ module Mongrel
|
|||
rescue Errno::EMFILE
|
||||
reap_dead_workers("too many open files")
|
||||
sleep 0.5
|
||||
rescue Errno::ECONNABORTED
|
||||
# client closed the socket even before accept
|
||||
client.close if not client.closed?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -702,364 +740,4 @@ module Mongrel
|
|||
end
|
||||
|
||||
|
||||
# Implements a simple DSL for configuring a Mongrel server for your
|
||||
# purposes. More used by framework implementers to setup Mongrel
|
||||
# how they like, but could be used by regular folks to add more things
|
||||
# to an existing mongrel configuration.
|
||||
#
|
||||
# It is used like this:
|
||||
#
|
||||
# require 'mongrel'
|
||||
# config = Mongrel::Configurator.new :host => "127.0.0.1" do
|
||||
# listener :port => 3000 do
|
||||
# uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml"))
|
||||
# end
|
||||
# run
|
||||
# end
|
||||
#
|
||||
# This will setup a simple DirHandler at the current directory and load additional
|
||||
# mime types from mimy.yaml. The :host => "127.0.0.1" is actually not
|
||||
# specific to the servers but just a hash of default parameters that all
|
||||
# server or uri calls receive.
|
||||
#
|
||||
# When you are inside the block after Mongrel::Configurator.new you can simply
|
||||
# call functions that are part of Configurator (like server, uri, daemonize, etc)
|
||||
# without having to refer to anything else. You can also call these functions on
|
||||
# the resulting object directly for additional configuration.
|
||||
#
|
||||
# A major thing about Configurator is that it actually lets you configure
|
||||
# multiple listeners for any hosts and ports you want. These are kept in a
|
||||
# map config.listeners so you can get to them.
|
||||
#
|
||||
# * :pid_file => Where to write the process ID.
|
||||
class Configurator
|
||||
attr_reader :listeners
|
||||
attr_reader :defaults
|
||||
attr_reader :needs_restart
|
||||
|
||||
# You pass in initial defaults and then a block to continue configuring.
|
||||
def initialize(defaults={}, &blk)
|
||||
@listener = nil
|
||||
@listener_name = nil
|
||||
@listeners = {}
|
||||
@defaults = defaults
|
||||
@needs_restart = false
|
||||
@pid_file = defaults[:pid_file]
|
||||
|
||||
if blk
|
||||
cloaker(&blk).bind(self).call
|
||||
end
|
||||
end
|
||||
|
||||
# Change privilege of the process to specified user and group.
|
||||
def change_privilege(user, group)
|
||||
begin
|
||||
if group
|
||||
log "Changing group to #{group}."
|
||||
Process::GID.change_privilege(Etc.getgrnam(group).gid)
|
||||
end
|
||||
|
||||
if user
|
||||
log "Changing user to #{user}."
|
||||
Process::UID.change_privilege(Etc.getpwnam(user).uid)
|
||||
end
|
||||
rescue Errno::EPERM
|
||||
log "FAILED to change user:group #{user}:#{group}: #$!"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# Writes the PID file but only if we're on windows.
|
||||
def write_pid_file
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
open(@pid_file,"w") {|f| f.write(Process.pid) }
|
||||
end
|
||||
end
|
||||
|
||||
# generates a class for cloaking the current self and making the DSL nicer
|
||||
def cloaking_class
|
||||
class << self
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Do not call this. You were warned.
|
||||
def cloaker(&blk)
|
||||
cloaking_class.class_eval do
|
||||
define_method :cloaker_, &blk
|
||||
meth = instance_method( :cloaker_ )
|
||||
remove_method :cloaker_
|
||||
meth
|
||||
end
|
||||
end
|
||||
|
||||
# This will resolve the given options against the defaults.
|
||||
# Normally just used internally.
|
||||
def resolve_defaults(options)
|
||||
options.merge(@defaults)
|
||||
end
|
||||
|
||||
# Starts a listener block. This is the only one that actually takes
|
||||
# a block and then you make Configurator.uri calls in order to setup
|
||||
# your URIs and handlers. If you write your Handlers as GemPlugins
|
||||
# then you can use load_plugins and plugin to load them.
|
||||
#
|
||||
# It expects the following options (or defaults):
|
||||
#
|
||||
# * :host => Host name to bind.
|
||||
# * :port => Port to bind.
|
||||
# * :num_processors => The maximum number of concurrent threads allowed. (950 default)
|
||||
# * :timeout => 1/100th of a second timeout between requests. (10 is 1/10th, 0 is timeout)
|
||||
# * :user => User to change to, must have :group as well.
|
||||
# * :group => Group to change to, must have :user as well.
|
||||
#
|
||||
def listener(options={},&blk)
|
||||
raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
|
||||
ops = resolve_defaults(options)
|
||||
ops[:num_processors] ||= 950
|
||||
ops[:timeout] ||= 0
|
||||
|
||||
@listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:timeout].to_i)
|
||||
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
||||
@listeners[@listener_name] = @listener
|
||||
|
||||
if ops[:user] and ops[:group]
|
||||
change_privilege(ops[:user], ops[:group])
|
||||
end
|
||||
|
||||
# Does the actual cloaking operation to give the new implicit self.
|
||||
if blk
|
||||
cloaker(&blk).bind(self).call
|
||||
end
|
||||
|
||||
# all done processing this listener setup, reset implicit variables
|
||||
@listener = nil
|
||||
@listener_name = nil
|
||||
end
|
||||
|
||||
|
||||
# Called inside a Configurator.listener block in order to
|
||||
# add URI->handler mappings for that listener. Use this as
|
||||
# many times as you like. It expects the following options
|
||||
# or defaults:
|
||||
#
|
||||
# * :handler => HttpHandler -- Handler to use for this location.
|
||||
# * :in_front => true/false -- Rather than appending, it prepends this handler.
|
||||
def uri(location, options={})
|
||||
ops = resolve_defaults(options)
|
||||
@listener.register(location, ops[:handler], in_front=ops[:in_front])
|
||||
end
|
||||
|
||||
|
||||
# Daemonizes the current Ruby script turning all the
|
||||
# listeners into an actual "server" or detached process.
|
||||
# You must call this *before* frameworks that open files
|
||||
# as otherwise the files will be closed by this function.
|
||||
#
|
||||
# Does not work for Win32 systems (the call is silently ignored).
|
||||
#
|
||||
# Requires the following options or defaults:
|
||||
#
|
||||
# * :cwd => Directory to change to.
|
||||
# * :log_file => Where to write STDOUT and STDERR.
|
||||
#
|
||||
# It is safe to call this on win32 as it will only require the daemons
|
||||
# gem/library if NOT win32.
|
||||
def daemonize(options={})
|
||||
ops = resolve_defaults(options)
|
||||
# save this for later since daemonize will hose it
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
require 'daemons/daemonize'
|
||||
|
||||
Daemonize.daemonize(log_file=File.join(ops[:cwd], ops[:log_file]))
|
||||
|
||||
# change back to the original starting directory
|
||||
Dir.chdir(ops[:cwd])
|
||||
|
||||
else
|
||||
log "WARNING: Win32 does not support daemon mode."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Uses the GemPlugin system to easily load plugins based on their
|
||||
# gem dependencies. You pass in either an :includes => [] or
|
||||
# :excludes => [] setting listing the names of plugins to include
|
||||
# or exclude from the when determining the dependencies.
|
||||
def load_plugins(options={})
|
||||
ops = resolve_defaults(options)
|
||||
|
||||
load_settings = {}
|
||||
if ops[:includes]
|
||||
ops[:includes].each do |plugin|
|
||||
load_settings[plugin] = GemPlugin::INCLUDE
|
||||
end
|
||||
end
|
||||
|
||||
if ops[:excludes]
|
||||
ops[:excludes].each do |plugin|
|
||||
load_settings[plugin] = GemPlugin::EXCLUDE
|
||||
end
|
||||
end
|
||||
|
||||
GemPlugin::Manager.instance.load(load_settings)
|
||||
end
|
||||
|
||||
|
||||
# Easy way to load a YAML file and apply default settings.
|
||||
def load_yaml(file, default={})
|
||||
default.merge(YAML.load_file(file))
|
||||
end
|
||||
|
||||
|
||||
# Loads the MIME map file and checks that it is correct
|
||||
# on loading. This is commonly passed to Mongrel::DirHandler
|
||||
# or any framework handler that uses DirHandler to serve files.
|
||||
# You can also include a set of default MIME types as additional
|
||||
# settings. See Mongrel::DirHandler for how the MIME types map
|
||||
# is organized.
|
||||
def load_mime_map(file, mime={})
|
||||
# configure any requested mime map
|
||||
mime = load_yaml(file, mime)
|
||||
|
||||
# check all the mime types to make sure they are the right format
|
||||
mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
|
||||
|
||||
return mime
|
||||
end
|
||||
|
||||
|
||||
# Loads and creates a plugin for you based on the given
|
||||
# name and configured with the selected options. The options
|
||||
# are merged with the defaults prior to passing them in.
|
||||
def plugin(name, options={})
|
||||
ops = resolve_defaults(options)
|
||||
GemPlugin::Manager.instance.create(name, ops)
|
||||
end
|
||||
|
||||
# Let's you do redirects easily as described in Mongrel::RedirectHandler.
|
||||
# You use it inside the configurator like this:
|
||||
#
|
||||
# redirect("/test", "/to/there") # simple
|
||||
# redirect("/to", /t/, 'w') # regexp
|
||||
# redirect("/hey", /(w+)/) {|match| ...} # block
|
||||
#
|
||||
def redirect(from, pattern, replacement = nil, &block)
|
||||
uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block)
|
||||
end
|
||||
|
||||
# Works like a meta run method which goes through all the
|
||||
# configured listeners. Use the Configurator.join method
|
||||
# to prevent Ruby from exiting until each one is done.
|
||||
def run
|
||||
@listeners.each {|name,s|
|
||||
s.run
|
||||
}
|
||||
|
||||
$mongrel_sleeper_thread = Thread.new { loop { sleep 1 } }
|
||||
end
|
||||
|
||||
# Calls .stop on all the configured listeners so they
|
||||
# stop processing requests (gracefully). By default it
|
||||
# assumes that you don't want to restart and that the pid file
|
||||
# should be unlinked on exit.
|
||||
def stop(needs_restart=false, unlink_pid_file=true)
|
||||
@listeners.each {|name,s|
|
||||
s.stop
|
||||
}
|
||||
|
||||
@needs_restart = needs_restart
|
||||
if unlink_pid_file
|
||||
File.unlink @pid_file if (@pid_file and File.exist?(@pid_file))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# This method should actually be called *outside* of the
|
||||
# Configurator block so that you can control it. In other words
|
||||
# do it like: config.join.
|
||||
def join
|
||||
@listeners.values.each {|s| s.acceptor.join }
|
||||
end
|
||||
|
||||
|
||||
# Calling this before you register your URIs to the given location
|
||||
# will setup a set of handlers that log open files, objects, and the
|
||||
# parameters for each request. This helps you track common problems
|
||||
# found in Rails applications that are either slow or become unresponsive
|
||||
# after a little while.
|
||||
#
|
||||
# You can pass an extra parameter *what* to indicate what you want to
|
||||
# debug. For example, if you just want to dump rails stuff then do:
|
||||
#
|
||||
# debug "/", what = [:rails]
|
||||
#
|
||||
# And it will only produce the log/mongrel_debug/rails.log file.
|
||||
# Available options are: :objects, :rails, :files, :threads, :params
|
||||
#
|
||||
# NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick.
|
||||
def debug(location, what = [:objects, :rails, :files, :threads, :params])
|
||||
require 'mongrel/debug'
|
||||
handlers = {
|
||||
:files => "/handlers/requestlog::access",
|
||||
:rails => "/handlers/requestlog::files",
|
||||
:objects => "/handlers/requestlog::objects",
|
||||
:threads => "/handlers/requestlog::threads",
|
||||
:params => "/handlers/requestlog::params"
|
||||
}
|
||||
|
||||
# turn on the debugging infrastructure, and ObjectTracker is a pig
|
||||
ObjectTracker.configure if what.include? :objects
|
||||
MongrelDbg.configure
|
||||
|
||||
# now we roll through each requested debug type, turn it on and load that plugin
|
||||
what.each do |type|
|
||||
MongrelDbg.begin_trace type
|
||||
uri location, :handler => plugin(handlers[type])
|
||||
end
|
||||
end
|
||||
|
||||
# Used to allow you to let users specify their own configurations
|
||||
# inside your Configurator setup. You pass it a script name and
|
||||
# it reads it in and does an eval on the contents passing in the right
|
||||
# binding so they can put their own Configurator statements.
|
||||
def run_config(script)
|
||||
open(script) {|f| eval(f.read, proc {self}) }
|
||||
end
|
||||
|
||||
# Sets up the standard signal handlers that are used on most Ruby
|
||||
# It only configures if the platform is not win32 and doesn't do
|
||||
# a HUP signal since this is typically framework specific.
|
||||
#
|
||||
# Requires a :pid_file option given to Configurator.new to indicate a file to delete.
|
||||
# It sets the MongrelConfig.needs_restart attribute if
|
||||
# the start command should reload. It's up to you to detect this
|
||||
# and do whatever is needed for a "restart".
|
||||
#
|
||||
# This command is safely ignored if the platform is win32 (with a warning)
|
||||
def setup_signals(options={})
|
||||
ops = resolve_defaults(options)
|
||||
|
||||
# forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C)
|
||||
trap("INT") { log "INT signal received."; stop(need_restart=false) }
|
||||
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
# graceful shutdown
|
||||
trap("TERM") { log "TERM signal received."; stop }
|
||||
|
||||
# restart
|
||||
trap("USR2") { log "USR2 signal received."; stop(need_restart=true) }
|
||||
|
||||
log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)."
|
||||
else
|
||||
log "Signals ready. INT => stop (no restart)."
|
||||
end
|
||||
end
|
||||
|
||||
# Logs a simple message to STDERR (or the mongrel log if in daemon mode).
|
||||
def log(msg)
|
||||
STDERR.print "** ", msg, "\n"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
366
lib/mongrel/configurator.rb
Normal file
366
lib/mongrel/configurator.rb
Normal file
|
@ -0,0 +1,366 @@
|
|||
require 'yaml'
|
||||
require 'etc'
|
||||
|
||||
|
||||
module Mongrel
|
||||
# Implements a simple DSL for configuring a Mongrel server for your
|
||||
# purposes. More used by framework implementers to setup Mongrel
|
||||
# how they like, but could be used by regular folks to add more things
|
||||
# to an existing mongrel configuration.
|
||||
#
|
||||
# It is used like this:
|
||||
#
|
||||
# require 'mongrel'
|
||||
# config = Mongrel::Configurator.new :host => "127.0.0.1" do
|
||||
# listener :port => 3000 do
|
||||
# uri "/app", :handler => Mongrel::DirHandler.new(".", load_mime_map("mime.yaml"))
|
||||
# end
|
||||
# run
|
||||
# end
|
||||
#
|
||||
# This will setup a simple DirHandler at the current directory and load additional
|
||||
# mime types from mimy.yaml. The :host => "127.0.0.1" is actually not
|
||||
# specific to the servers but just a hash of default parameters that all
|
||||
# server or uri calls receive.
|
||||
#
|
||||
# When you are inside the block after Mongrel::Configurator.new you can simply
|
||||
# call functions that are part of Configurator (like server, uri, daemonize, etc)
|
||||
# without having to refer to anything else. You can also call these functions on
|
||||
# the resulting object directly for additional configuration.
|
||||
#
|
||||
# A major thing about Configurator is that it actually lets you configure
|
||||
# multiple listeners for any hosts and ports you want. These are kept in a
|
||||
# map config.listeners so you can get to them.
|
||||
#
|
||||
# * :pid_file => Where to write the process ID.
|
||||
class Configurator
|
||||
attr_reader :listeners
|
||||
attr_reader :defaults
|
||||
attr_reader :needs_restart
|
||||
|
||||
# You pass in initial defaults and then a block to continue configuring.
|
||||
def initialize(defaults={}, &blk)
|
||||
@listener = nil
|
||||
@listener_name = nil
|
||||
@listeners = {}
|
||||
@defaults = defaults
|
||||
@needs_restart = false
|
||||
@pid_file = defaults[:pid_file]
|
||||
|
||||
if blk
|
||||
cloaker(&blk).bind(self).call
|
||||
end
|
||||
end
|
||||
|
||||
# Change privilege of the process to specified user and group.
|
||||
def change_privilege(user, group)
|
||||
begin
|
||||
if group
|
||||
log "Changing group to #{group}."
|
||||
Process::GID.change_privilege(Etc.getgrnam(group).gid)
|
||||
end
|
||||
|
||||
if user
|
||||
log "Changing user to #{user}."
|
||||
Process::UID.change_privilege(Etc.getpwnam(user).uid)
|
||||
end
|
||||
rescue Errno::EPERM
|
||||
log "FAILED to change user:group #{user}:#{group}: #$!"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# Writes the PID file but only if we're on windows.
|
||||
def write_pid_file
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
open(@pid_file,"w") {|f| f.write(Process.pid) }
|
||||
end
|
||||
end
|
||||
|
||||
# generates a class for cloaking the current self and making the DSL nicer
|
||||
def cloaking_class
|
||||
class << self
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# Do not call this. You were warned.
|
||||
def cloaker(&blk)
|
||||
cloaking_class.class_eval do
|
||||
define_method :cloaker_, &blk
|
||||
meth = instance_method( :cloaker_ )
|
||||
remove_method :cloaker_
|
||||
meth
|
||||
end
|
||||
end
|
||||
|
||||
# This will resolve the given options against the defaults.
|
||||
# Normally just used internally.
|
||||
def resolve_defaults(options)
|
||||
options.merge(@defaults)
|
||||
end
|
||||
|
||||
# Starts a listener block. This is the only one that actually takes
|
||||
# a block and then you make Configurator.uri calls in order to setup
|
||||
# your URIs and handlers. If you write your Handlers as GemPlugins
|
||||
# then you can use load_plugins and plugin to load them.
|
||||
#
|
||||
# It expects the following options (or defaults):
|
||||
#
|
||||
# * :host => Host name to bind.
|
||||
# * :port => Port to bind.
|
||||
# * :num_processors => The maximum number of concurrent threads allowed. (950 default)
|
||||
# * :timeout => 1/100th of a second timeout between requests. (10 is 1/10th, 0 is timeout)
|
||||
# * :user => User to change to, must have :group as well.
|
||||
# * :group => Group to change to, must have :user as well.
|
||||
#
|
||||
def listener(options={},&blk)
|
||||
raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
|
||||
ops = resolve_defaults(options)
|
||||
ops[:num_processors] ||= 950
|
||||
ops[:timeout] ||= 0
|
||||
|
||||
@listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:timeout].to_i)
|
||||
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
||||
@listeners[@listener_name] = @listener
|
||||
|
||||
if ops[:user] and ops[:group]
|
||||
change_privilege(ops[:user], ops[:group])
|
||||
end
|
||||
|
||||
# Does the actual cloaking operation to give the new implicit self.
|
||||
if blk
|
||||
cloaker(&blk).bind(self).call
|
||||
end
|
||||
|
||||
# all done processing this listener setup, reset implicit variables
|
||||
@listener = nil
|
||||
@listener_name = nil
|
||||
end
|
||||
|
||||
|
||||
# Called inside a Configurator.listener block in order to
|
||||
# add URI->handler mappings for that listener. Use this as
|
||||
# many times as you like. It expects the following options
|
||||
# or defaults:
|
||||
#
|
||||
# * :handler => HttpHandler -- Handler to use for this location.
|
||||
# * :in_front => true/false -- Rather than appending, it prepends this handler.
|
||||
def uri(location, options={})
|
||||
ops = resolve_defaults(options)
|
||||
@listener.register(location, ops[:handler], in_front=ops[:in_front])
|
||||
end
|
||||
|
||||
|
||||
# Daemonizes the current Ruby script turning all the
|
||||
# listeners into an actual "server" or detached process.
|
||||
# You must call this *before* frameworks that open files
|
||||
# as otherwise the files will be closed by this function.
|
||||
#
|
||||
# Does not work for Win32 systems (the call is silently ignored).
|
||||
#
|
||||
# Requires the following options or defaults:
|
||||
#
|
||||
# * :cwd => Directory to change to.
|
||||
# * :log_file => Where to write STDOUT and STDERR.
|
||||
#
|
||||
# It is safe to call this on win32 as it will only require the daemons
|
||||
# gem/library if NOT win32.
|
||||
def daemonize(options={})
|
||||
ops = resolve_defaults(options)
|
||||
# save this for later since daemonize will hose it
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
require 'daemons/daemonize'
|
||||
|
||||
Daemonize.daemonize(log_file=File.join(ops[:cwd], ops[:log_file]))
|
||||
|
||||
# change back to the original starting directory
|
||||
Dir.chdir(ops[:cwd])
|
||||
|
||||
else
|
||||
log "WARNING: Win32 does not support daemon mode."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Uses the GemPlugin system to easily load plugins based on their
|
||||
# gem dependencies. You pass in either an :includes => [] or
|
||||
# :excludes => [] setting listing the names of plugins to include
|
||||
# or exclude from the when determining the dependencies.
|
||||
def load_plugins(options={})
|
||||
ops = resolve_defaults(options)
|
||||
|
||||
load_settings = {}
|
||||
if ops[:includes]
|
||||
ops[:includes].each do |plugin|
|
||||
load_settings[plugin] = GemPlugin::INCLUDE
|
||||
end
|
||||
end
|
||||
|
||||
if ops[:excludes]
|
||||
ops[:excludes].each do |plugin|
|
||||
load_settings[plugin] = GemPlugin::EXCLUDE
|
||||
end
|
||||
end
|
||||
|
||||
GemPlugin::Manager.instance.load(load_settings)
|
||||
end
|
||||
|
||||
|
||||
# Easy way to load a YAML file and apply default settings.
|
||||
def load_yaml(file, default={})
|
||||
default.merge(YAML.load_file(file))
|
||||
end
|
||||
|
||||
|
||||
# Loads the MIME map file and checks that it is correct
|
||||
# on loading. This is commonly passed to Mongrel::DirHandler
|
||||
# or any framework handler that uses DirHandler to serve files.
|
||||
# You can also include a set of default MIME types as additional
|
||||
# settings. See Mongrel::DirHandler for how the MIME types map
|
||||
# is organized.
|
||||
def load_mime_map(file, mime={})
|
||||
# configure any requested mime map
|
||||
mime = load_yaml(file, mime)
|
||||
|
||||
# check all the mime types to make sure they are the right format
|
||||
mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
|
||||
|
||||
return mime
|
||||
end
|
||||
|
||||
|
||||
# Loads and creates a plugin for you based on the given
|
||||
# name and configured with the selected options. The options
|
||||
# are merged with the defaults prior to passing them in.
|
||||
def plugin(name, options={})
|
||||
ops = resolve_defaults(options)
|
||||
GemPlugin::Manager.instance.create(name, ops)
|
||||
end
|
||||
|
||||
# Let's you do redirects easily as described in Mongrel::RedirectHandler.
|
||||
# You use it inside the configurator like this:
|
||||
#
|
||||
# redirect("/test", "/to/there") # simple
|
||||
# redirect("/to", /t/, 'w') # regexp
|
||||
# redirect("/hey", /(w+)/) {|match| ...} # block
|
||||
#
|
||||
def redirect(from, pattern, replacement = nil, &block)
|
||||
uri from, :handler => Mongrel::RedirectHandler.new(pattern, replacement, &block)
|
||||
end
|
||||
|
||||
# Works like a meta run method which goes through all the
|
||||
# configured listeners. Use the Configurator.join method
|
||||
# to prevent Ruby from exiting until each one is done.
|
||||
def run
|
||||
@listeners.each {|name,s|
|
||||
s.run
|
||||
}
|
||||
|
||||
$mongrel_sleeper_thread = Thread.new { loop { sleep 1 } }
|
||||
end
|
||||
|
||||
# Calls .stop on all the configured listeners so they
|
||||
# stop processing requests (gracefully). By default it
|
||||
# assumes that you don't want to restart and that the pid file
|
||||
# should be unlinked on exit.
|
||||
def stop(needs_restart=false, unlink_pid_file=true)
|
||||
@listeners.each {|name,s|
|
||||
s.stop
|
||||
}
|
||||
|
||||
@needs_restart = needs_restart
|
||||
if unlink_pid_file
|
||||
File.unlink @pid_file if (@pid_file and File.exist?(@pid_file))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# This method should actually be called *outside* of the
|
||||
# Configurator block so that you can control it. In other words
|
||||
# do it like: config.join.
|
||||
def join
|
||||
@listeners.values.each {|s| s.acceptor.join }
|
||||
end
|
||||
|
||||
|
||||
# Calling this before you register your URIs to the given location
|
||||
# will setup a set of handlers that log open files, objects, and the
|
||||
# parameters for each request. This helps you track common problems
|
||||
# found in Rails applications that are either slow or become unresponsive
|
||||
# after a little while.
|
||||
#
|
||||
# You can pass an extra parameter *what* to indicate what you want to
|
||||
# debug. For example, if you just want to dump rails stuff then do:
|
||||
#
|
||||
# debug "/", what = [:rails]
|
||||
#
|
||||
# And it will only produce the log/mongrel_debug/rails.log file.
|
||||
# Available options are: :objects, :rails, :files, :threads, :params
|
||||
#
|
||||
# NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick.
|
||||
def debug(location, what = [:objects, :rails, :files, :threads, :params])
|
||||
require 'mongrel/debug'
|
||||
handlers = {
|
||||
:files => "/handlers/requestlog::access",
|
||||
:rails => "/handlers/requestlog::files",
|
||||
:objects => "/handlers/requestlog::objects",
|
||||
:threads => "/handlers/requestlog::threads",
|
||||
:params => "/handlers/requestlog::params"
|
||||
}
|
||||
|
||||
# turn on the debugging infrastructure, and ObjectTracker is a pig
|
||||
ObjectTracker.configure if what.include? :objects
|
||||
MongrelDbg.configure
|
||||
|
||||
# now we roll through each requested debug type, turn it on and load that plugin
|
||||
what.each do |type|
|
||||
MongrelDbg.begin_trace type
|
||||
uri location, :handler => plugin(handlers[type])
|
||||
end
|
||||
end
|
||||
|
||||
# Used to allow you to let users specify their own configurations
|
||||
# inside your Configurator setup. You pass it a script name and
|
||||
# it reads it in and does an eval on the contents passing in the right
|
||||
# binding so they can put their own Configurator statements.
|
||||
def run_config(script)
|
||||
open(script) {|f| eval(f.read, proc {self}) }
|
||||
end
|
||||
|
||||
# Sets up the standard signal handlers that are used on most Ruby
|
||||
# It only configures if the platform is not win32 and doesn't do
|
||||
# a HUP signal since this is typically framework specific.
|
||||
#
|
||||
# Requires a :pid_file option given to Configurator.new to indicate a file to delete.
|
||||
# It sets the MongrelConfig.needs_restart attribute if
|
||||
# the start command should reload. It's up to you to detect this
|
||||
# and do whatever is needed for a "restart".
|
||||
#
|
||||
# This command is safely ignored if the platform is win32 (with a warning)
|
||||
def setup_signals(options={})
|
||||
ops = resolve_defaults(options)
|
||||
|
||||
# forced shutdown, even if previously restarted (actually just like TERM but for CTRL-C)
|
||||
trap("INT") { log "INT signal received."; stop(need_restart=false) }
|
||||
|
||||
if RUBY_PLATFORM !~ /mswin/
|
||||
# graceful shutdown
|
||||
trap("TERM") { log "TERM signal received."; stop }
|
||||
|
||||
# restart
|
||||
trap("USR2") { log "USR2 signal received."; stop(need_restart=true) }
|
||||
|
||||
log "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart)."
|
||||
else
|
||||
log "Signals ready. INT => stop (no restart)."
|
||||
end
|
||||
end
|
||||
|
||||
# Logs a simple message to STDERR (or the mongrel log if in daemon mode).
|
||||
def log(msg)
|
||||
STDERR.print "** ", msg, "\n"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -77,7 +77,7 @@ module Mongrel
|
|||
rescue Errno::EPIPE
|
||||
# ignored
|
||||
rescue Object => rails_error
|
||||
STDERR.puts "Error calling Dispatcher.dispatch #{rails_error.inspect}"
|
||||
STDERR.puts "#{Tim.now}: Error calling Dispatcher.dispatch #{rails_error.inspect}"
|
||||
STDERR.puts rails_error.backtrace.join("\n")
|
||||
ensure
|
||||
@guard.unlock unless ActionController::Base.allow_concurrency
|
||||
|
@ -136,6 +136,7 @@ module Mongrel
|
|||
ops[:environment] ||= "development"
|
||||
ops[:docroot] ||= "public"
|
||||
ops[:mime] ||= {}
|
||||
ops[:prefix] ||= "/"
|
||||
|
||||
|
||||
$orig_dollar_quote = $".clone
|
||||
|
@ -148,6 +149,9 @@ module Mongrel
|
|||
if ActionController::Base.allow_concurrency
|
||||
log "[RAILS] ActionController::Base.allow_concurrency is true. Wow, you're very brave."
|
||||
end
|
||||
|
||||
ActionController::AbstractRequest.relative_url_root = ops[:prefix]
|
||||
|
||||
@rails_handler = RailsHandler.new(ops[:docroot], ops[:mime])
|
||||
end
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ class WebServerTest < Test::Unit::TestCase
|
|||
@server = HttpServer.new("127.0.0.1", 9998,num_processors=1)
|
||||
@tester = TestHandler.new
|
||||
@server.register("/test", @tester)
|
||||
@server.run
|
||||
redirect_test_io do
|
||||
@server.run
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
|
|
Loading…
Reference in a new issue