diff options
author | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-03-27 06:10:07 +0000 |
---|---|---|
committer | zedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9> | 2006-03-27 06:10:07 +0000 |
commit | 110e92752bd182459a08db3fcb8eb4a48d2f846f (patch) | |
tree | 0ad5651600105f898f8b4d5f0e8d4e571f996e01 | |
parent | 4d9966ee00b597cff3b58c851aef65c3801d9077 (diff) | |
download | unicorn-110e92752bd182459a08db3fcb8eb4a48d2f846f.tar.gz |
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@124 19e92222-5c0b-0410-8929-a290d50e31e9
-rw-r--r-- | bin/mongrel_rails_svc | 246 | ||||
-rw-r--r-- | lib/mongrel.rb | 6 | ||||
-rw-r--r-- | lib/mongrel/handlers.rb | 46 | ||||
-rw-r--r-- | lib/mongrel/rails.rb | 171 |
4 files changed, 253 insertions, 216 deletions
diff --git a/bin/mongrel_rails_svc b/bin/mongrel_rails_svc index 5fa4caa..8056bcf 100644 --- a/bin/mongrel_rails_svc +++ b/bin/mongrel_rails_svc @@ -4,6 +4,7 @@ # This is where Win32::Daemon resides.
###############################################
require 'rubygems'
+require 'mongrel'
require 'mongrel/rails'
require 'optparse'
require 'win32/service'
@@ -70,14 +71,15 @@ class MongrelRails def delayed_initialize
dbg "delayed_initialize entered"
-
+
@rails = configure_rails
# start up mongrel with the right configurations
@server = Mongrel::HttpServer.new(@ip, @port, @num_procs.to_i, @timeout.to_i)
@server.register("/", @rails)
-
+
dbg "delayed_initialize left"
+
end
def load_mime_map
@@ -104,11 +106,12 @@ class MongrelRails Dir.chdir(@rails_root)
+
ENV['RAILS_ENV'] = @environment
- require File.join(@rails_root, 'config/environment')
+ require 'config/environment'
# configure the rails handler
- rails = RailsHandler.new(@docroot, load_mime_map)
+ rails = Mongrel::Rails::RailsHandler.new(@docroot, load_mime_map)
dbg "configure_rails left"
@@ -116,24 +119,29 @@ class MongrelRails end
def start_serve
- dbg "start_serve entered"
-
- @runner = Thread.new do
- dbg_th "runner_thread suspended"
- Thread.stop
+ begin
+ dbg "start_serve entered"
+
+ @runner = Thread.new do
+ dbg_th "runner_thread suspended"
+ Thread.stop
+
+ dbg_th "runner_thread resumed"
+ dbg_th "runner_thread acceptor.join"
+ @server.acceptor.join
+ end
- dbg_th "runner_thread resumed"
- dbg_th "runner_thread acceptor.join"
- @server.acceptor.join
+ dbg "server.run"
+ @server.run
+
+ dbg "runner.run"
+ @runner.run
+
+ dbg "start_serve left"
+ rescue
+ dbg "ERROR: #$!\r\n"
+ dbg $!.backtrace.join("\r\n")
end
-
- dbg "server.run"
- @server.run
-
- dbg "runner.run"
- @runner.run
-
- dbg "start_serve left"
end
def stop_serve
@@ -193,99 +201,107 @@ class RailsDaemon < Win32::Daemon end
-if ARGV[0] == 'service'
- ARGV.shift
-
- # default options
- OPTIONS = {
- :rails_root => Dir.pwd,
- :environment => 'production',
- :ip => '0.0.0.0',
- :port => 3000,
- :mime_map => nil,
- :num_procs => 1024,
- :timeout => 0,
- :cpu => nil
- }
-
- ARGV.options do |opts|
- opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
- opts.on('-e', '--environment ENV', "Rails environment to run as. (default: production)") { |OPTIONS[:environment]| }
- opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
- opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
- opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
- opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
- opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
- opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
-
- opts.parse!
- end
-
- #expand RAILS_ROOT
- OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
-
- OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
-
- # We must bind to a specific cpu?
- if OPTIONS[:cpu]
- Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
- end
-
- rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
- rails_svc = RailsDaemon.new(rails)
- rails_svc.mainloop
-
-elsif ARGV[0] == 'debug'
- ARGV.shift
-
- # default options
- OPTIONS = {
- :rails_root => Dir.pwd,
- :environment => 'production',
- :ip => '0.0.0.0',
- :port => 3000,
- :mime_map => nil,
- :num_procs => 20,
- :timeout => 120,
- :cpu => nil
- }
-
- ARGV.options do |opts|
- opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
- opts.on('-e', '--environment ENV', "Rails environment to run as.") { |OPTIONS[:environment]| }
- opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
- opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
- opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
- opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
- opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
- opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
-
- opts.parse!
- end
-
- #expand RAILS_ROOT
- OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
-
- OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
-
- # We must bind to a specific cpu?
- if OPTIONS[:cpu]
- Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
- end
-
- rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
- rails.delayed_initialize
- rails.start_serve
-
- begin
- sleep
- rescue Interrupt
- puts "graceful shutdown?"
- end
-
- begin
- rails.stop_serve
- rescue
- end
-
+begin
+ if ARGV[0] == 'service'
+ ARGV.shift
+
+ # default options
+ OPTIONS = {
+ :rails_root => Dir.pwd,
+ :environment => 'production',
+ :ip => '0.0.0.0',
+ :port => 3000,
+ :mime_map => nil,
+ :num_procs => 1024,
+ :timeout => 0,
+ :cpu => nil
+ }
+
+ ARGV.options do |opts|
+ opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
+ opts.on('-e', '--environment ENV', "Rails environment to run as. (default: production)") { |OPTIONS[:environment]| }
+ opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
+ opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
+ opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
+ opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
+ opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
+ opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
+
+ opts.parse!
+ end
+
+ #expand RAILS_ROOT
+ OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
+
+ OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
+
+ # We must bind to a specific cpu?
+ if OPTIONS[:cpu]
+ Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
+ end
+
+ rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
+ rails_svc = RailsDaemon.new(rails)
+ rails_svc.mainloop
+
+ elsif ARGV[0] == 'debug'
+ ARGV.shift
+
+ # default options
+ OPTIONS = {
+ :rails_root => Dir.pwd,
+ :environment => 'production',
+ :ip => '0.0.0.0',
+ :port => 3000,
+ :mime_map => nil,
+ :num_procs => 20,
+ :timeout => 120,
+ :cpu => nil
+ }
+
+ ARGV.options do |opts|
+ opts.on('-r', '--root PATH', "Set the root path where your rails app resides.") { |OPTIONS[:rails_root]| }
+ opts.on('-e', '--environment ENV', "Rails environment to run as.") { |OPTIONS[:environment]| }
+ opts.on('-b', '--binding ADDR', "Address to bind to") { |OPTIONS[:ip]| }
+ opts.on('-p', '--port PORT', "Which port to bind to") { |OPTIONS[:port]| }
+ opts.on('-m', '--mime PATH', "A YAML file that lists additional MIME types") { |OPTIONS[:mime_map]| }
+ opts.on('-P', '--num-procs INT', "Number of processor threads to use") { |OPTIONS[:num_procs]| }
+ opts.on('-t', '--timeout SECONDS', "Timeout all requests after SECONDS time") { |OPTIONS[:timeout]| }
+ opts.on('-c', '--cpu CPU', "Bind the process to specific cpu") { |OPTIONS[:cpu]| }
+
+ opts.parse!
+ end
+
+ #expand RAILS_ROOT
+ OPTIONS[:rails_root] = File.expand_path(OPTIONS[:rails_root])
+
+ OPTIONS[:docroot] = File.expand_path(OPTIONS[:rails_root] + '/public')
+
+ # We must bind to a specific cpu?
+ if OPTIONS[:cpu]
+ Kernel32.set_affinity(Process.pid, OPTIONS[:cpu])
+ end
+
+ rails = MongrelRails.new(OPTIONS[:ip], OPTIONS[:port], OPTIONS[:rails_root], OPTIONS[:docroot], OPTIONS[:environment], OPTIONS[:mime_map], OPTIONS[:num_procs].to_i, OPTIONS[:timeout].to_i)
+ rails.delayed_initialize
+ rails.start_serve
+
+ begin
+ sleep
+ rescue Interrupt
+ dbg "ERROR: #$!\r\n"
+ dbg $!.backtrace.join("\r\n")
+ puts "graceful shutdown?"
+ end
+
+ begin
+ rails.stop_serve
+ rescue
+ dbg "ERROR: #$!\r\n"
+ dbg $!.backtrace.join("\r\n")
+ end
+ end
+rescue
+ dbg "ERROR: #$!\r\n"
+ dbg $!.backtrace.join("\r\n")
end
diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 59402cc..19edf1e 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -200,6 +200,7 @@ module Mongrel @out.write(value) @out.write("\r\n") end + end # Writes and controls your response to the client using the HTTP/1.1 specification. @@ -306,6 +307,10 @@ module Mongrel end end + def write(data) + @socket.write(data) + end + # This takes whatever has been done to header and body and then writes it in the # proper format to make an HTTP/1.1 response. def finished @@ -317,6 +322,7 @@ module Mongrel def done (@status_sent and @header_sent and @body_sent) end + end diff --git a/lib/mongrel/handlers.rb b/lib/mongrel/handlers.rb index 7d817bb..d0de0ca 100644 --- a/lib/mongrel/handlers.rb +++ b/lib/mongrel/handlers.rb @@ -1,3 +1,11 @@ +require 'rubygems' +begin + require 'sendfile' + $mongrel_has_sendfile = true + STDERR.puts "** You have sendfile installed, will use that to serve files." +rescue Object + $mongrel_has_sendfile = false +end module Mongrel @@ -150,20 +158,29 @@ module Mongrel # Sends the contents of a file back to the user. Not terribly efficient since it's # opening and closing the file for each read. def send_file(req, response) - response.start(200) do |head,out| - # set the mime type from our map based on the ending - dot_at = req.rindex(".") - if dot_at - ext = req[dot_at .. -1] - if MIME_TYPES[ext] - head['Content-Type'] = MIME_TYPES[ext] - end - end - open(req, "rb") do |f| - out.write(f.read) + # first we setup the headers and status then we do a very fast send on the socket directly + response.status = 200 + + # set the mime type from our map based on the ending + dot_at = req.rindex(".") + if dot_at + ext = req[dot_at .. -1] + if MIME_TYPES[ext] + response.header['Content-Type'] = MIME_TYPES[ext] end end + + response.header['Content-Length'] = File.size(req) + + response.send_status + response.send_header + + if $mongrel_has_sendfile + File.open(req, "rb") { |f| response.socket.sendfile(f) } + else + File.open(req, "rb") { |f| response.socket.write(f.read) } + end end @@ -184,11 +201,8 @@ module Mongrel send_file(req, response) end rescue => details - response.reset - response.start(403) do |head,out| - out << "Error accessing file: #{details}" - out << details.backtrace.join("\n") - end + STDERR.puts "Error accessing file: #{details}" + STDERR.puts details.backtrace.join("\n") end end end diff --git a/lib/mongrel/rails.rb b/lib/mongrel/rails.rb index 8dc42a0..d2c714c 100644 --- a/lib/mongrel/rails.rb +++ b/lib/mongrel/rails.rb @@ -4,6 +4,92 @@ require 'cgi' module Mongrel module Rails + + # Implements a handler that can run Rails and serve files out of the + # Rails application's public directory. This lets you run your Rails + # application with Mongrel during development and testing, then use it + # also in production behind a server that's better at serving the + # static files. + # + # The RailsHandler takes a mime_map parameter which is a simple suffix=mimetype + # mapping that it should add to the list of valid mime types. + # + # It also supports page caching directly and will try to resolve a request + # in the following order: + # + # * If the requested exact PATH_INFO exists as a file then serve it. + # * If it exists at PATH_INFO+".html" exists then serve that. + # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go. + # + # This means that if you are using page caching it will actually work with Mongrel + # and you should see a decent speed boost (but not as fast as if you use lighttpd). + # + # An additional feature you can use is + class RailsHandler < Mongrel::HttpHandler + attr_reader :files + attr_reader :guard + + def initialize(dir, mime_map = {}) + @files = Mongrel::DirHandler.new(dir,false) + @guard = Mutex.new + + # register the requested mime types + mime_map.each {|k,v| Mongrel::DirHandler::add_mime_type(k,v) } + end + + # Attempts to resolve the request as follows: + # + # + # * If the requested exact PATH_INFO exists as a file then serve it. + # * If it exists at PATH_INFO+".html" exists then serve that. + # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go. + def process(request, response) + return if response.socket.closed? + + path_info = request.params[Mongrel::Const::PATH_INFO] + page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html" + + if @files.can_serve(path_info) + # File exists as-is so serve it up + @files.process(request,response) + elsif @files.can_serve(page_cached) + # possible cached page, serve it up + request.params[Mongrel::Const::PATH_INFO] = page_cached + @files.process(request,response) + else + begin + cgi = Mongrel::CGIWrapper.new(request, response) + cgi.handler = self + + @guard.synchronize do + # Rails is not thread safe so must be run entirely within synchronize + Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body) + end + + # This finalizes the output using the proper HttpResponse way + cgi.out {""} + rescue Errno::EPIPE + # ignored + rescue Object => rails_error + STDERR.puts "Error calling Dispatcher.dispatch #{rails_error.inspect}" + STDERR.puts rails_error.backtrace.join("\n") + end + end + end + + + # Does the internal reload for Rails. It might work for most cases, but + # sometimes you get exceptions. In that case just do a real restart. + def reload! + @guard.synchronize do + $".replace $orig_dollar_quote + GC.start + Dispatcher.reset_application! + ActionController::Routing::Routes.reload + end + end + end + # Creates Rails specific configuration options for people to use # instead of the base Configurator. class RailsConfigurator < Mongrel::Configurator @@ -86,91 +172,6 @@ module Mongrel log "WARNING: Rails does not support signals on Win32." end end - - # Implements a handler that can run Rails and serve files out of the - # Rails application's public directory. This lets you run your Rails - # application with Mongrel during development and testing, then use it - # also in production behind a server that's better at serving the - # static files. - # - # The RailsHandler takes a mime_map parameter which is a simple suffix=mimetype - # mapping that it should add to the list of valid mime types. - # - # It also supports page caching directly and will try to resolve a request - # in the following order: - # - # * If the requested exact PATH_INFO exists as a file then serve it. - # * If it exists at PATH_INFO+".html" exists then serve that. - # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go. - # - # This means that if you are using page caching it will actually work with Mongrel - # and you should see a decent speed boost (but not as fast as if you use lighttpd). - # - # An additional feature you can use is - class RailsHandler < Mongrel::HttpHandler - attr_reader :files - attr_reader :guard - - def initialize(dir, mime_map = {}) - @files = Mongrel::DirHandler.new(dir,false) - @guard = Mutex.new - - # register the requested mime types - mime_map.each {|k,v| Mongrel::DirHandler::add_mime_type(k,v) } - end - - # Attempts to resolve the request as follows: - # - # - # * If the requested exact PATH_INFO exists as a file then serve it. - # * If it exists at PATH_INFO+".html" exists then serve that. - # * Finally, construct a Mongrel::CGIWrapper and run Dispatcher.dispath to have Rails go. - def process(request, response) - return if response.socket.closed? - - path_info = request.params[Mongrel::Const::PATH_INFO] - page_cached = request.params[Mongrel::Const::PATH_INFO] + ".html" - - if @files.can_serve(path_info) - # File exists as-is so serve it up - @files.process(request,response) - elsif @files.can_serve(page_cached) - # possible cached page, serve it up - request.params[Mongrel::Const::PATH_INFO] = page_cached - @files.process(request,response) - else - begin - cgi = Mongrel::CGIWrapper.new(request, response) - cgi.handler = self - - @guard.synchronize do - # Rails is not thread safe so must be run entirely within synchronize - Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body) - end - - # This finalizes the output using the proper HttpResponse way - cgi.out {""} - rescue Errno::EPIPE - # ignored - rescue Object => rails_error - log "Error calling Dispatcher.dispatch #{rails_error.inspect}" - log rails_error.backtrace.join("\n") - end - end - end - - - # Does the internal reload for Rails. It might work for most cases, but - # sometimes you get exceptions. In that case just do a real restart. - def reload! - @guard.synchronize do - $".replace $orig_dollar_quote - GC.start - Dispatcher.reset_application! - ActionController::Routing::Routes.reload - end - end - end end end end |