about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--bin/mongrel_rails_svc246
-rw-r--r--lib/mongrel.rb6
-rw-r--r--lib/mongrel/handlers.rb46
-rw-r--r--lib/mongrel/rails.rb171
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