about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-02-21 00:55:39 +0000
committerzedshaw <zedshaw@19e92222-5c0b-0410-8929-a290d50e31e9>2006-02-21 00:55:39 +0000
commite5c2f9404a4864cadc6d01bf174a4c33c39fadc0 (patch)
tree9b8bce67c4293b6bcc7a4d94b12335e9d90d5341
parentffb373d6027cf579ff432473883b86ceb5c7df72 (diff)
downloadunicorn-e5c2f9404a4864cadc6d01bf174a4c33c39fadc0.tar.gz
git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@53 19e92222-5c0b-0410-8929-a290d50e31e9
-rw-r--r--bin/mongrel_rails1
-rw-r--r--examples/simpletest.rb2
-rw-r--r--lib/mongrel.rb343
-rw-r--r--lib/mongrel/cgi.rb147
-rw-r--r--lib/mongrel/handlers.rb180
5 files changed, 340 insertions, 333 deletions
diff --git a/bin/mongrel_rails b/bin/mongrel_rails
index ed391b1..8c3ccc5 100644
--- a/bin/mongrel_rails
+++ b/bin/mongrel_rails
@@ -121,6 +121,7 @@ class StartCommand < Mongrel::Command::Command
     server = Mongrel::HttpServer.new(@address, @port, @num_procs.to_i, @timeout.to_i)
     server.register("/", rails)
     server.run
+    trap("INT") { server.stop }
     
     begin
       puts "Server ready."
diff --git a/examples/simpletest.rb b/examples/simpletest.rb
index e7a1011..438f69e 100644
--- a/examples/simpletest.rb
+++ b/examples/simpletest.rb
@@ -24,7 +24,7 @@ if ARGV.length != 3
   exit(1)
 end
 
-h = Mongrel::HttpServer.new(ARGV[0], ARGV[1])
+h = Mongrel::HttpServer.new(ARGV[0], ARGV[1].to_i)
 h.register("/", SimpleHandler.new)
 h.register("/files", Mongrel::DirHandler.new(ARGV[2]))
 h.run
diff --git a/lib/mongrel.rb b/lib/mongrel.rb
index 239172d..d4d3941 100644
--- a/lib/mongrel.rb
+++ b/lib/mongrel.rb
@@ -2,8 +2,8 @@ require 'socket'
 require 'http11'
 require 'thread'
 require 'stringio'
-require 'cgi'
-
+require 'mongrel/cgi'
+require 'mongrel/handlers'
 
 # Mongrel module containing all of the classes (include C extensions) for running
 # a Mongrel web server.  It contains a minimalist HTTP server with just enough
@@ -156,6 +156,14 @@ module Mongrel
       # fix up the CGI requirements
       params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0
       params[Const::CONTENT_TYPE] = params[Const::HTTP_CONTENT_TYPE] if params[Const::HTTP_CONTENT_TYPE]
+      params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
+      params[Const::REMOTE_ADDR]=socket.peeraddr[3]
+      host,port = params[Const::HTTP_HOST].split(":")
+      params[Const::SERVER_NAME]=host
+      params[Const::SERVER_PORT]=port if port
+      params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
+      params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
+
 
       # now, if the initial_body isn't long enough for the content length we have to fill it
       # TODO: adapt for big ass stuff by writing to a temp file
@@ -277,16 +285,6 @@ module Mongrel
   end
   
 
-  # You implement your application handler with this.  It's very light giving
-  # just the minimum necessary for you to handle a request and shoot back
-  # a response.  Look at the HttpRequest and HttpResponse objects for how
-  # to use them.
-  class HttpHandler
-    def process(request, response)
-    end
-  end
-
-
   # This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
   # make up the majority of how the server functions.  It's a very simple class that just
   # has a thread accepting connections and a simple HttpServer.process_client function
@@ -362,14 +360,6 @@ module Mongrel
             if handler
               params[Const::PATH_INFO] = path_info
               params[Const::SCRIPT_NAME] = script_name
-              params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
-              params[Const::REMOTE_ADDR]=client.peeraddr[3]
-              host,port = params[Const::HTTP_HOST].split(":")
-              params[Const::SERVER_NAME]=host
-              params[Const::SERVER_PORT]=port if port
-              params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
-              params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
-
               request = HttpRequest.new(params, data[nread ... data.length], client)
               response = HttpResponse.new(client)
               handler.process(request, response)
@@ -456,317 +446,6 @@ module Mongrel
 
   end
 
+end
 
-  # The server normally returns a 404 response if a URI is requested, but it
-  # also returns a lame empty message.  This lets you do a 404 response
-  # with a custom message for special URIs.
-  class Error404Handler < HttpHandler
-
-    # Sets the message to return.  This is constructed once for the handler
-    # so it's pretty efficient.
-    def initialize(msg)
-      @response = Const::ERROR_404_RESPONSE + msg
-    end
-    
-    # Just kicks back the standard 404 response with your special message.
-    def process(request, response)
-      response.socket.write(@response)
-    end
-
-  end
-
-
-  # Serves the contents of a directory.  You give it the path to the root
-  # where the files are located, and it tries to find the files based on
-  # the PATH_INFO inside the directory.  If the requested path is a
-  # directory then it returns a simple directory listing.
-  #
-  # It does a simple protection against going outside it's root path by
-  # converting all paths to an absolute expanded path, and then making sure
-  # that the final expanded path includes the root path.  If it doesn't
-  # than it simply gives a 404.
-  class DirHandler < HttpHandler
-    MIME_TYPES = {
-      ".css"        =>  "text/css",
-      ".gif"        =>  "image/gif",
-      ".htm"        =>  "text/html",
-      ".html"       =>  "text/html",
-      ".jpeg"       =>  "image/jpeg",
-      ".jpg"        =>  "image/jpeg",
-      ".js"         =>  "text/javascript",
-      ".png"        =>  "image/png",
-      ".swf"        =>  "application/x-shockwave-flash",
-      ".txt"        =>  "text/plain"
-    }
-
-
-    attr_reader :path
-
-    # You give it the path to the directory root and an (optional)
-    def initialize(path, listing_allowed=true, index_html="index.html")
-      @path = File.expand_path(path)
-      @listing_allowed=listing_allowed
-      @index_html = index_html
-    end
-
-    # Checks if the given path can be served and returns the full path (or nil if not).
-    def can_serve(path_info)
-      req = File.expand_path(File.join(@path,path_info), @path)
-
-      if req.index(@path) == 0 and File.exist? req
-        # it exists and it's in the right location
-        if File.directory? req
-          # the request is for a directory
-          index = File.join(req, @index_html)
-          if File.exist? index
-            # serve the index
-            return index
-          elsif @listing_allowed
-            # serve the directory
-            req
-          else
-            # do not serve anything
-            return nil
-          end
-        else
-          # it's a file and it's there
-          return req
-        end
-      else
-        # does not exist or isn't in the right spot
-        return nil
-      end
-    end
-
-
-    # Returns a simplistic directory listing if they're enabled, otherwise a 403.
-    # Base is the base URI from the REQUEST_URI, dir is the directory to serve
-    # on the file system (comes from can_serve()), and response is the HttpResponse
-    # object to send the results on.
-    def send_dir_listing(base, dir, response)
-      # take off any trailing / so the links come out right
-      base.chop! if base[-1] == "/"[-1]
-
-      if @listing_allowed
-        response.start(200) do |head,out|
-          head['Content-Type'] = "text/html"
-          out << "<html><head><title>Directory Listing</title></head><body>"
-          Dir.entries(dir).each do |child|
-            next if child == "."
-
-            if child == ".."
-              out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
-            else
-              out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
-            end
-          end
-          out << "</body></html>"
-        end
-      else
-        response.start(403) do |head,out|
-          out.write("Directory listings not allowed")
-        end
-      end
-    end
-
-    
-    # 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)
-        end
-      end
-    end
-
-
-    # Process the request to either serve a file or a directory listing
-    # if allowed (based on the listing_allowed paramter to the constructor).
-    def process(request, response)
-      req = can_serve request.params['PATH_INFO']
-      if not req
-        # not found, return a 404
-        response.start(404) do |head,out|
-          out << "File not found"
-        end
-      else
-        begin
-          if File.directory? req
-            send_dir_listing(request.params["REQUEST_URI"],req, response)
-          else
-            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
-        end
-      end
-    end
-
-    # There is a small number of default mime types for extensions, but
-    # this lets you add any others you'll need when serving content.
-    def DirHandler::add_mime_type(extension, type)
-      MIME_TYPES[extension] = type
-    end
-
-  end
-
-
-  # The beginning of a complete wrapper around Mongrel's internal HTTP processing
-  # system but maintaining the original Ruby CGI module.  Use this only as a crutch
-  # to get existing CGI based systems working.  It should handle everything, but please
-  # notify me if you see special warnings.  This work is still very alpha so I need
-  # testers to help work out the various corner cases.
-  class CGIWrapper < ::CGI
-    public :env_table
-    attr_reader :options
-
-    # these are stripped out of any keys passed to CGIWrapper.header function
-    REMOVED_KEYS = [ "nph","status","server","connection","type",
-                     "charset","length","language","expires"]
-
-    # Takes an HttpRequest and HttpResponse object, plus any additional arguments
-    # normally passed to CGI.  These are used internally to create a wrapper around
-    # the real CGI while maintaining Mongrel's view of the world.
-    def initialize(request, response, *args)
-      @request = request
-      @response = response
-      @args = *args
-      @input = StringIO.new(request.body)
-      @head = {}
-      @out_called = false
-      super(*args)
-    end
-    
-    # The header is typically called to send back the header.  In our case we
-    # collect it into a hash for later usage.
-    #
-    # nph -- Mostly ignored.  It'll output the date.
-    # connection -- Completely ignored.  Why is CGI doing this?
-    # length -- Ignored since Mongrel figures this out from what you write to output.
-    #
-    def header(options = "text/html")
-      # if they pass in a string then just write the Content-Type
-      if options.class == String
-        @head['Content-Type'] = options unless @head['Content-Type']
-      else
-        # convert the given options into what Mongrel wants
-        @head['Content-Type'] = options['type'] || "text/html"
-        @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
-        
-        # setup date only if they use nph
-        @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
-
-        # setup the server to use the default or what they set
-        @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
-
-        # remaining possible options they can give
-        @head['Status'] = options['status'] if options['status']
-        @head['Content-Language'] = options['language'] if options['language']
-        @head['Expires'] = options['expires'] if options['expires']
-
-        # drop the keys we don't want anymore
-        REMOVED_KEYS.each {|k| options.delete(k) }
-
-        # finally just convert the rest raw (which puts 'cookie' directly)
-        # 'cookie' is translated later as we write the header out
-        options.each{|k,v| @head[k] = v}
-      end
-
-      # doing this fakes out the cgi library to think the headers are empty
-      # we then do the real headers in the out function call later
-      ""
-    end
-
-    # Takes any 'cookie' setting and sends it over the Mongrel header,
-    # then removes the setting from the options. If cookie is an
-    # Array or Hash then it sends those on with .to_s, otherwise
-    # it just calls .to_s on it and hopefully your "cookie" can
-    # write itself correctly.
-    def send_cookies(to)
-      # convert the cookies based on the myriad of possible ways to set a cookie
-      if @head['cookie']
-        cookie = @head['cookie']
-        case cookie
-        when Array
-          cookie.each {|c| to['Set-Cookie'] = c.to_s }
-        when Hash
-          cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
-        else
-          to['Set-Cookie'] = options['cookie'].to_s
-        end
-        
-        @head.delete('cookie')
-
-        # @output_cookies seems to never be used, but we'll process it just in case
-        @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
-      end
-    end
-    
-    # The dumb thing is people can call header or this or both and in any order.
-    # So, we just reuse header and then finalize the HttpResponse the right way.
-    # Status is taken from the various options and converted to what Mongrel needs
-    # via the CGIWrapper.status function.
-    def out(options = "text/html")
-      return if @out_called  # don't do this more than once
-
-      header(options)
-
-      @response.start status do |head, out|
-        send_cookies(head)
-        
-        @head.each {|k,v| head[k] = v}
-        out.write(yield || "")
-      end
-    end
-    
-    # Computes the status once, but lazily so that people who call header twice
-    # don't get penalized.  Because CGI insists on including the options status
-    # message in the status we have to do a bit of parsing.
-    def status
-      if not @status
-        stat = @head["Status"]
-        stat = stat.split(' ')[0] if stat
-
-        @status = stat || "200"
-      end
-
-      @status
-    end
-
-    # Used to wrap the normal args variable used inside CGI.
-    def args
-      @args
-    end
-    
-    # Used to wrap the normal env_table variable used inside CGI.
-    def env_table
-      @request.params
-    end
-    
-    # Used to wrap the normal stdinput variable used inside CGI.
-    def stdinput
-      @input
-    end
-    
-    # The stdoutput should be completely bypassed but we'll drop a warning just in case
-    def stdoutput
-      STDERR.puts "WARNING: Your program is doing something not expected.  Please tell Zed that stdoutput was used and what software you are running.  Thanks."
-      @response.body
-    end    
-  end
 
-end
diff --git a/lib/mongrel/cgi.rb b/lib/mongrel/cgi.rb
new file mode 100644
index 0000000..3d7d25a
--- /dev/null
+++ b/lib/mongrel/cgi.rb
@@ -0,0 +1,147 @@
+require 'cgi'
+
+module Mongrel
+    # The beginning of a complete wrapper around Mongrel's internal HTTP processing
+  # system but maintaining the original Ruby CGI module.  Use this only as a crutch
+  # to get existing CGI based systems working.  It should handle everything, but please
+  # notify me if you see special warnings.  This work is still very alpha so I need
+  # testers to help work out the various corner cases.
+  class CGIWrapper < ::CGI
+    public :env_table
+    attr_reader :options
+
+    # these are stripped out of any keys passed to CGIWrapper.header function
+    REMOVED_KEYS = [ "nph","status","server","connection","type",
+                     "charset","length","language","expires"]
+
+    # Takes an HttpRequest and HttpResponse object, plus any additional arguments
+    # normally passed to CGI.  These are used internally to create a wrapper around
+    # the real CGI while maintaining Mongrel's view of the world.
+    def initialize(request, response, *args)
+      @request = request
+      @response = response
+      @args = *args
+      @input = StringIO.new(request.body)
+      @head = {}
+      @out_called = false
+      super(*args)
+    end
+    
+    # The header is typically called to send back the header.  In our case we
+    # collect it into a hash for later usage.
+    #
+    # nph -- Mostly ignored.  It'll output the date.
+    # connection -- Completely ignored.  Why is CGI doing this?
+    # length -- Ignored since Mongrel figures this out from what you write to output.
+    #
+    def header(options = "text/html")
+      # if they pass in a string then just write the Content-Type
+      if options.class == String
+        @head['Content-Type'] = options unless @head['Content-Type']
+      else
+        # convert the given options into what Mongrel wants
+        @head['Content-Type'] = options['type'] || "text/html"
+        @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
+        
+        # setup date only if they use nph
+        @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
+
+        # setup the server to use the default or what they set
+        @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
+
+        # remaining possible options they can give
+        @head['Status'] = options['status'] if options['status']
+        @head['Content-Language'] = options['language'] if options['language']
+        @head['Expires'] = options['expires'] if options['expires']
+
+        # drop the keys we don't want anymore
+        REMOVED_KEYS.each {|k| options.delete(k) }
+
+        # finally just convert the rest raw (which puts 'cookie' directly)
+        # 'cookie' is translated later as we write the header out
+        options.each{|k,v| @head[k] = v}
+      end
+
+      # doing this fakes out the cgi library to think the headers are empty
+      # we then do the real headers in the out function call later
+      ""
+    end
+
+    # Takes any 'cookie' setting and sends it over the Mongrel header,
+    # then removes the setting from the options. If cookie is an
+    # Array or Hash then it sends those on with .to_s, otherwise
+    # it just calls .to_s on it and hopefully your "cookie" can
+    # write itself correctly.
+    def send_cookies(to)
+      # convert the cookies based on the myriad of possible ways to set a cookie
+      if @head['cookie']
+        cookie = @head['cookie']
+        case cookie
+        when Array
+          cookie.each {|c| to['Set-Cookie'] = c.to_s }
+        when Hash
+          cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
+        else
+          to['Set-Cookie'] = options['cookie'].to_s
+        end
+        
+        @head.delete('cookie')
+
+        # @output_cookies seems to never be used, but we'll process it just in case
+        @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
+      end
+    end
+    
+    # The dumb thing is people can call header or this or both and in any order.
+    # So, we just reuse header and then finalize the HttpResponse the right way.
+    # Status is taken from the various options and converted to what Mongrel needs
+    # via the CGIWrapper.status function.
+    def out(options = "text/html")
+      return if @out_called  # don't do this more than once
+
+      header(options)
+
+      @response.start status do |head, out|
+        send_cookies(head)
+        
+        @head.each {|k,v| head[k] = v}
+        out.write(yield || "")
+      end
+    end
+    
+    # Computes the status once, but lazily so that people who call header twice
+    # don't get penalized.  Because CGI insists on including the options status
+    # message in the status we have to do a bit of parsing.
+    def status
+      if not @status
+        stat = @head["Status"]
+        stat = stat.split(' ')[0] if stat
+
+        @status = stat || "200"
+      end
+
+      @status
+    end
+
+    # Used to wrap the normal args variable used inside CGI.
+    def args
+      @args
+    end
+    
+    # Used to wrap the normal env_table variable used inside CGI.
+    def env_table
+      @request.params
+    end
+    
+    # Used to wrap the normal stdinput variable used inside CGI.
+    def stdinput
+      @input
+    end
+    
+    # The stdoutput should be completely bypassed but we'll drop a warning just in case
+    def stdoutput
+      STDERR.puts "WARNING: Your program is doing something not expected.  Please tell Zed that stdoutput was used and what software you are running.  Thanks."
+      @response.body
+    end    
+  end
+end
diff --git a/lib/mongrel/handlers.rb b/lib/mongrel/handlers.rb
new file mode 100644
index 0000000..609a252
--- /dev/null
+++ b/lib/mongrel/handlers.rb
@@ -0,0 +1,180 @@
+
+module Mongrel
+
+  # You implement your application handler with this.  It's very light giving
+  # just the minimum necessary for you to handle a request and shoot back
+  # a response.  Look at the HttpRequest and HttpResponse objects for how
+  # to use them.
+  class HttpHandler
+    def process(request, response)
+    end
+  end
+
+
+  # The server normally returns a 404 response if an unknown URI is requested, but it
+  # also returns a lame empty message.  This lets you do a 404 response
+  # with a custom message for special URIs.
+  class Error404Handler < HttpHandler
+
+    # Sets the message to return.  This is constructed once for the handler
+    # so it's pretty efficient.
+    def initialize(msg)
+      @response = Const::ERROR_404_RESPONSE + msg
+    end
+    
+    # Just kicks back the standard 404 response with your special message.
+    def process(request, response)
+      response.socket.write(@response)
+    end
+
+  end
+
+
+  # Serves the contents of a directory.  You give it the path to the root
+  # where the files are located, and it tries to find the files based on
+  # the PATH_INFO inside the directory.  If the requested path is a
+  # directory then it returns a simple directory listing.
+  #
+  # It does a simple protection against going outside it's root path by
+  # converting all paths to an absolute expanded path, and then making sure
+  # that the final expanded path includes the root path.  If it doesn't
+  # than it simply gives a 404.
+  class DirHandler < HttpHandler
+    MIME_TYPES = {
+      ".css"        =>  "text/css",
+      ".gif"        =>  "image/gif",
+      ".htm"        =>  "text/html",
+      ".html"       =>  "text/html",
+      ".jpeg"       =>  "image/jpeg",
+      ".jpg"        =>  "image/jpeg",
+      ".js"         =>  "text/javascript",
+      ".png"        =>  "image/png",
+      ".swf"        =>  "application/x-shockwave-flash",
+      ".txt"        =>  "text/plain"
+    }
+
+
+    attr_reader :path
+
+    # You give it the path to the directory root and an (optional)
+    def initialize(path, listing_allowed=true, index_html="index.html")
+      @path = File.expand_path(path)
+      @listing_allowed=listing_allowed
+      @index_html = index_html
+    end
+
+    # Checks if the given path can be served and returns the full path (or nil if not).
+    def can_serve(path_info)
+      req = File.expand_path(File.join(@path,path_info), @path)
+
+      if req.index(@path) == 0 and File.exist? req
+        # it exists and it's in the right location
+        if File.directory? req
+          # the request is for a directory
+          index = File.join(req, @index_html)
+          if File.exist? index
+            # serve the index
+            return index
+          elsif @listing_allowed
+            # serve the directory
+            req
+          else
+            # do not serve anything
+            return nil
+          end
+        else
+          # it's a file and it's there
+          return req
+        end
+      else
+        # does not exist or isn't in the right spot
+        return nil
+      end
+    end
+
+
+    # Returns a simplistic directory listing if they're enabled, otherwise a 403.
+    # Base is the base URI from the REQUEST_URI, dir is the directory to serve
+    # on the file system (comes from can_serve()), and response is the HttpResponse
+    # object to send the results on.
+    def send_dir_listing(base, dir, response)
+      # take off any trailing / so the links come out right
+      base.chop! if base[-1] == "/"[-1]
+
+      if @listing_allowed
+        response.start(200) do |head,out|
+          head['Content-Type'] = "text/html"
+          out << "<html><head><title>Directory Listing</title></head><body>"
+          Dir.entries(dir).each do |child|
+            next if child == "."
+
+            if child == ".."
+              out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
+            else
+              out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
+            end
+          end
+          out << "</body></html>"
+        end
+      else
+        response.start(403) do |head,out|
+          out.write("Directory listings not allowed")
+        end
+      end
+    end
+
+    
+    # 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)
+        end
+      end
+    end
+
+
+    # Process the request to either serve a file or a directory listing
+    # if allowed (based on the listing_allowed paramter to the constructor).
+    def process(request, response)
+      req = can_serve request.params['PATH_INFO']
+      if not req
+        # not found, return a 404
+        response.start(404) do |head,out|
+          out << "File not found"
+        end
+      else
+        begin
+          if File.directory? req
+            send_dir_listing(request.params["REQUEST_URI"],req, response)
+          else
+            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
+        end
+      end
+    end
+
+    # There is a small number of default mime types for extensions, but
+    # this lets you add any others you'll need when serving content.
+    def DirHandler::add_mime_type(extension, type)
+      MIME_TYPES[extension] = type
+    end
+
+  end
+end