about summary refs log tree commit homepage
path: root/lib/mongrel
diff options
context:
space:
mode:
authorzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-02-28 07:04:41 +0000
committerzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-02-28 07:04:41 +0000
commitd1a01c03f71c14e5d4fe66d93c5ab444c0aba554 (patch)
treed7d4df48f1e708dfceea7bd78907177e5b1be889 /lib/mongrel
parent4e5132f63a210beb766ebfe52bea7424903403ae (diff)
downloadunicorn-d1a01c03f71c14e5d4fe66d93c5ab444c0aba554.tar.gz
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@65 19e92222-5c0b-0410-8929-a290d50e31e9
Diffstat (limited to 'lib/mongrel')
-rw-r--r--lib/mongrel/command.rb38
-rw-r--r--lib/mongrel/plugins.rb78
-rw-r--r--lib/mongrel/rails.rb68
3 files changed, 149 insertions, 35 deletions
diff --git a/lib/mongrel/command.rb b/lib/mongrel/command.rb
index 3625438..04af386 100644
--- a/lib/mongrel/command.rb
+++ b/lib/mongrel/command.rb
@@ -1,7 +1,6 @@
 require 'singleton'
 require 'optparse'
-require 'pluginfactory'
-
+require 'mongrel/plugins'
 
 module Mongrel
 
@@ -10,17 +9,10 @@ module Mongrel
 
   module Command
 
-
     # A Command pattern implementation used to create the set of command available to the user
     # from Mongrel.  The script uses objects which implement this interface to do the
     # user's bidding.
-    #
-    # Creating a new command is very easy, and you can do it without modifying the source
-    # of Mongrel thanks to PluginFactory.  What you do is the following:
-    #
-    # 1.  
-    class Command
-      include PluginFactory
+    module Command
       
       attr_reader :valid, :done_validating
 
@@ -65,12 +57,6 @@ module Mongrel
         @opt.parse! argv
       end
       
-      # Tells the PluginFactory where to look for additional commands.  By default
-      # it's just a "plugins" directory wherever we are located.
-      def self.derivativeDirs
-        return ["plugins"]
-      end
-      
       # Returns true/false depending on whether the command is configured properly.
       def validate
         return @valid
@@ -119,8 +105,6 @@ module Mongrel
       end
     end
     
-    
-    
     # A Singleton class that manages all of the available commands
     # and handles running them.
     class Registry
@@ -128,15 +112,9 @@ module Mongrel
       
       # Builds a list of possible commands from the Command derivates list
       def commands
-        list = Command.derivatives()
-        match = Regexp.new("(.*::.*)|(.*command.*)", Regexp::IGNORECASE)
-        
-        results = []
-        list.keys.each do |key|
-          results << key.to_s unless match.match(key.to_s)
-        end
-        
-        return results.sort
+        pmgr = PluginManager.instance
+        list = pmgr.available["/commands"]
+        return list.sort
       end
 
       # Prints a list of available commands.
@@ -144,7 +122,7 @@ module Mongrel
         puts "Available commands are:\n\n"
         
         self.commands.each do |name|
-          puts " - #{name}\n"
+          puts " - #{name[1 .. -1]}\n"
         end
         
         puts "\nEach command takes -h as an option to get help."
@@ -165,8 +143,8 @@ module Mongrel
         
         # command exists, set it up and validate it
         begin
-          command = Command.create(cmd_name, args)
-        rescue FactoryError
+          command = PluginManager.instance.create("/commands/#{cmd_name}", args)
+        rescue
           STDERR.puts "INVALID COMMAND: #$!"
           print_command_list
           return
diff --git a/lib/mongrel/plugins.rb b/lib/mongrel/plugins.rb
index 9778e9b..f312e36 100644
--- a/lib/mongrel/plugins.rb
+++ b/lib/mongrel/plugins.rb
@@ -1,6 +1,47 @@
 require 'singleton'
 
 module Mongrel
+
+  # Implements the main method of managing plugins for Mongrel.
+  # "Plugins" in this sense are any classes which get registered
+  # with Mongrel for possible use when it's operating.  These can
+  # be Handlers, Commands, or other classes.  When you create a
+  # Plugin you register it into a URI-like namespace that makes
+  # it easy for you (and others) to reference it later during
+  # configuration.
+  #
+  # PluginManager is used as nothing more than a holder of all the
+  # plugins that have registered themselves.  Let's say you have:
+  #
+  #  class StopNow < Plugin "/commands"
+  #   ...
+  #  end
+  #
+  # Then you can get at this plugin with:
+  #
+  #  cmd = PluginManager.create("/commands/stopnow")
+  #
+  # The funky syntax for StopNow is a weird trick borrowed from
+  # the Camping framework. See the Mongrel::Plugin *function* (yes,
+  # function).  What this basically does is register it
+  # into the namespace for plugins at /commands.  You could go
+  # as arbitrarily nested as you like.
+  #
+  # Why this strange almost second namespace?  Why not just use
+  # the ObjectSpace and/or Modules?  The main reason is speed and
+  # to avoid cluttering the Ruby namespace with what is really a
+  # configuration statement.  This lets implementors put code
+  # into the Ruby structuring they need, and still have Plugins
+  # available to Mongrel via simple URI-like names.
+  #
+  # The alternative (as pluginfactory does it) is to troll through
+  # ObjectSpace looking for stuff that *might* be plugins every time
+  # one is needed.  This alternative also means that you are stuck
+  # naming your commands in specific ways and putting them in specific
+  # modules in order to configure how Mongrel should use them.
+  #
+  # One downside to this is that you need to subclass plugin to
+  # make it work.  In this case use mixins to add other functionality.
   class PluginManager
     include Singleton
     
@@ -8,6 +49,9 @@ module Mongrel
       @plugins = URIClassifier.new
     end
     
+    # Tell the PluginManager to scan the given path (recursively)
+    # and load the *.rb files found there.  This is how you'd
+    # setup your own plugin directory.
     def load(path)
       Dir.chdir(path) do
         Dir["**/*.rb"].each do |rbfile|
@@ -16,6 +60,9 @@ module Mongrel
       end
     end
     
+    # Not necessary for you to call directly, but this is
+    # how Mongrel::PluginBase.inherited actually adds a
+    # plugin to a category.
     def register(category, name, klass)
       cat, ignored, map = @plugins.resolve(category)
       if not cat
@@ -26,17 +73,21 @@ module Mongrel
       end
     end
     
-    
+    # Resolves the given name (should include /category/name) to
+    # find the plugin class and create an instance.  It uses
+    # the same URIClassifier that the rest of Mongrel does so it
+    # is fast.
     def create(name, options = {})
       category, plugin, map = @plugins.resolve(name)
       if category and plugin and plugin.length > 0
-        STDERR.puts "found: #{category} #{plugin} for #{name}"
         map[plugin].new(options)
       else
         raise "Plugin #{name} does not exist"
       end
     end
     
+    # Returns a map of URIs->[handlers] that you can
+    # use to investigate available handlers.
     def available
       map = {}
       @plugins.uris.each do |u|
@@ -50,19 +101,36 @@ module Mongrel
     
   end
 
+  # This base class for plugins reallys does nothing
+  # more than wire up the new class into the right category.
+  # It is not thread-safe yet but will be soon.
   class PluginBase
     
+    # See Mongrel::Plugin for an explanation.
     def PluginBase.inherited(klass)
-      
-      PluginManager.instance.register(@@category, klass.to_s.downcase, klass)
+      name = "/" + klass.to_s.downcase
+      PluginManager.instance.register(@@category, name, klass)
     end
     
+    # See Mongrel::Plugin for an explanation.
     def PluginBase.category=(category)
       @@category = category
     end
   end
   
-  def Plugin(c)
+  # This nifty function works with the PluginBase to give you
+  # the syntax:
+  #
+  #  class MyThing < Plugin "/things"
+  #    ...
+  #  end
+  #
+  # What it does is temporarily sets the PluginBase.category, and then
+  # returns PluginBase.  Since the next immediate thing Ruby does is
+  # use this returned class to create the new class, PluginBase.inherited
+  # gets called.  PluginBase.inherited then uses the set category, class name,
+  # and class to register the plugin in the right way.
+  def Mongrel::Plugin(c)
     PluginBase.category = c
     PluginBase
   end
diff --git a/lib/mongrel/rails.rb b/lib/mongrel/rails.rb
new file mode 100644
index 0000000..73d4272
--- /dev/null
+++ b/lib/mongrel/rails.rb
@@ -0,0 +1,68 @@
+require 'mongrel'
+
+
+# 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).
+class RailsHandler < Mongrel::HttpHandler
+  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["PATH_INFO"]
+    page_cached = request.params["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["PATH_INFO"] = page_cached
+      @files.process(request,response)
+    else
+      cgi = Mongrel::CGIWrapper.new(request, response)
+
+      begin
+        @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 Object => rails_error
+        STDERR.puts "calling Dispatcher.dispatch #{rails_error}"
+        STDERR.puts rails_error.backtrace.join("\n")
+      end
+    end
+  end
+
+end