about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEvan Weaver <eweaver@twitter.com>2009-01-31 21:40:40 -0800
committerEvan Weaver <eweaver@twitter.com>2009-01-31 21:40:40 -0800
commitd426c68c11d5733dd4462caafe92b28f436f30e7 (patch)
tree659c33e6abd2cebe4bd544996d36e9c58f83b0e4
parentcf4717de742266b4e62ad8bf075c6de974a73267 (diff)
downloadunicorn-d426c68c11d5733dd4462caafe92b28f436f30e7.tar.gz
-rw-r--r--CHANGELOG2
-rw-r--r--ext/http11/http11.c7
-rw-r--r--lib/mongrel.rb67
-rw-r--r--lib/mongrel/const.rb11
-rw-r--r--lib/mongrel/http_request.rb68
-rw-r--r--test/unit/test_http_parser.rb17
6 files changed, 54 insertions, 118 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 6075d77..8fa519f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,6 @@
 
+v2.0. Rack support.
+
 v1.1.4. Fix camping handler. Correct treatment of @throttle parameter.
 
 v1.1.3. Fix security flaw of DirHandler; reported on mailing list.
diff --git a/ext/http11/http11.c b/ext/http11/http11.c
index a228019..6b3ea34 100644
--- a/ext/http11/http11.c
+++ b/ext/http11/http11.c
@@ -20,7 +20,6 @@ static VALUE cHttpParser;
 static VALUE eHttpParserError;
 
 #define id_handler_map rb_intern("@handler_map")
-#define id_http_body rb_intern("@http_body")
 #define HTTP_PREFIX "HTTP_"
 #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
 
@@ -34,6 +33,7 @@ static VALUE global_http_content_length;
 static VALUE global_request_path;
 static VALUE global_content_type;
 static VALUE global_http_content_type;
+static VALUE global_http_body;
 static VALUE global_gateway_interface;
 static VALUE global_gateway_interface_value;
 static VALUE global_server_name;
@@ -306,8 +306,8 @@ void header_done(void *data, const char *at, size_t length)
     }
   }
 
-  /* grab the initial body and stuff it into an ivar */
-  rb_ivar_set(req, id_http_body, rb_str_new(at, length));
+  /* grab the initial body and stuff it into the hash */
+  rb_hash_aset(req, global_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);
 }
@@ -499,6 +499,7 @@ void Init_http11()
   DEF_GLOBAL(request_path, "REQUEST_PATH");
   DEF_GLOBAL(content_length, "CONTENT_LENGTH");
   DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
+  DEF_GLOBAL(http_body, "HTTP_BODY");
   DEF_GLOBAL(content_type, "CONTENT_TYPE");
   DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
   DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
diff --git a/lib/mongrel.rb b/lib/mongrel.rb
index b81f07e..39659a2 100644
--- a/lib/mongrel.rb
+++ b/lib/mongrel.rb
@@ -33,11 +33,6 @@ module Mongrel
     # A logger instance that conforms to the API of stdlib's Logger.
     attr_accessor :logger
     
-    # By default, will return an instance of stdlib's Logger logging to STDERR
-    def logger
-      @logger ||= Logger.new(STDERR)
-    end
-    
     def run(app, options = {})
       HttpServer.new(app, options).start.join
     end
@@ -52,11 +47,6 @@ module Mongrel
   # Thrown by HttpServer#stop if the server is not started.
   class AcceptorError < StandardError; end
 
-  # A Hash with one extra parameter for the HTTP body, used internally.
-  class HttpParams < Hash
-    attr_accessor :http_body
-  end
-
   #
   # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier
   # make up the majority of how the server functions.  It's a very simple class that just
@@ -64,20 +54,15 @@ module Mongrel
   # to do the heavy lifting with the IO and Ruby.  
   #
   class HttpServer
-    attr_reader :acceptor
-    attr_reader :workers
-    attr_reader :host
-    attr_reader :port
-    attr_reader :timeout
-    attr_reader :max_queued_threads
-    attr_reader :max_concurrent_threads
+    attr_reader :acceptor, :workers, :logger, :host, :port, :timeout, :max_queued_threads, :max_concurrent_threads
     
     DEFAULTS = {
-      :Max_queued_threads => 20,
-      :Max_concurrent_threads => 20,
-      :Timeout => 60,
-      :Host => '0.0.0.0',
-      :Port => 8080
+      :timeout => 60,
+      :host => '0.0.0.0',
+      :port => 8080,
+      :logger => Logger.new(STDERR),
+      :max_queued_threads => 20,
+      :max_concurrent_threads => 20      
     }
 
     # Creates a working server on host:port (strange things happen if port isn't a Number).
@@ -110,7 +95,7 @@ module Mongrel
     def process_client(client)
       begin
         parser = HttpParser.new
-        params = HttpParams.new
+        params = Hash.new
         request = nil
         data = client.readpartial(Const::CHUNK_SIZE)
         nparsed = 0
@@ -123,13 +108,13 @@ module Mongrel
           nparsed = parser.execute(params, data, nparsed)
 
           if parser.finished?
-            if not params[Const::REQUEST_PATH]
-              # it might be a dumbass full host request header
+            if !params[Const::REQUEST_PATH]
+              # It might be a dumbass full host request header
               uri = URI.parse(params[Const::REQUEST_URI])
               params[Const::REQUEST_PATH] = uri.path
             end
 
-            raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
+            raise "No REQUEST PATH" if !params[Const::REQUEST_PATH]
 
             params[Const::PATH_INFO] = params[Const::REQUEST_PATH]
             params[Const::SCRIPT_NAME] = Const::SLASH
@@ -142,8 +127,8 @@ module Mongrel
             #  or other intermediary acting on behalf of the actual source client."
             params[Const::REMOTE_ADDR] = client.peeraddr.last
 
-            # select handlers that want more detailed request notification
-            request = HttpRequest.new(params, client)
+            # Select handlers that want more detailed request notification
+            request = HttpRequest.new(params, client, logger)
 
             # 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
@@ -164,21 +149,21 @@ module Mongrel
       rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
         client.close rescue nil
       rescue HttpParserError => e
-        Mongrel.logger.error "#{Time.now}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
-        Mongrel.logger.error "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
+        logger.error "#HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
+        logger.error "#REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
       rescue Errno::EMFILE
         reap_dead_workers('too many files')
       rescue Object => e
-        Mongrel.logger.error "#{Time.now}: Read error: #{e.inspect}"
-        Mongrel.logger.error e.backtrace.join("\n")
+        logger.error "#Read error: #{e.inspect}"
+        logger.error e.backtrace.join("\n")
       ensure
         begin
           client.close
         rescue IOError
           # Already closed
         rescue Object => e
-          Mongrel.logger.error "#{Time.now}: Client error: #{e.inspect}"
-          Mongrel.logger.error e.backtrace.join("\n")
+          logger.error "#Client error: #{e.inspect}"
+          logger.error e.backtrace.join("\n")
         end
         request.body.close! if request and request.body.class == Tempfile
       end
@@ -190,14 +175,14 @@ module Mongrel
     # after the reap is done.  It only runs if there are workers to reap.
     def reap_dead_workers(reason='unknown')
       if @workers.list.length > 0
-        Mongrel.logger.info "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
+        logger.info "#Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
         error_msg = "Mongrel timed out this thread: #{reason}"
         mark = Time.now
         @workers.list.each do |worker|
           worker[:started_on] = Time.now if not worker[:started_on]
 
           if mark - worker[:started_on] > @timeout
-            Mongrel.logger.info "Thread #{worker.inspect} is too old, killing."
+            logger.info "Thread #{worker.inspect} is too old, killing."
             worker.raise(TimeoutError.new(error_msg))
           end
         end
@@ -211,7 +196,7 @@ module Mongrel
     # via mongrel_rails.
     def graceful_shutdown
       while reap_dead_workers("shutdown") > 0
-        Mongrel.logger.info "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout} seconds."
+        logger.info "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout} seconds."
         sleep @timeout / 10
       end
     end
@@ -257,7 +242,7 @@ module Mongrel
   
               worker_list = @workers.list
               if worker_list.length >= @max_queued_threads
-                Mongrel.logger.error "Server overloaded with #{worker_list.length} processors (#@max_queued_threads max). Dropping connection."
+                logger.error "Server overloaded with #{worker_list.length} processors (#@max_queued_threads max). Dropping connection."
                 client.close rescue nil
                 reap_dead_workers("max processors")
               else
@@ -274,14 +259,14 @@ module Mongrel
               # client closed the socket even before accept
               client.close rescue nil
             rescue Object => e
-              Mongrel.logger.error "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
-              Mongrel.logger.error e.backtrace.join("\n")
+              logger.error "Unhandled listen loop exception #{e.inspect}."
+              logger.error e.backtrace.join("\n")
             end
           end
           graceful_shutdown
         ensure
           @socket.close
-          # Mongrel.logger.info "#{Time.now}: Closed socket."
+          logger.info "Closed socket."
         end
       end
 
diff --git a/lib/mongrel/const.rb b/lib/mongrel/const.rb
index a9b100e..994a2bf 100644
--- a/lib/mongrel/const.rb
+++ b/lib/mongrel/const.rb
@@ -53,10 +53,13 @@ module Mongrel
   # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
   # too taxing on performance.
   module Const
-    DATE = "Date".freeze
+    DATE="Date".freeze
 
-    # This is the part of the path after the SCRIPT_NAME.  URIClassifier will determine this.
+    # This is the part of the path after the SCRIPT_NAME.
     PATH_INFO="PATH_INFO".freeze
+    
+    # Request body
+    HTTP_BODY="HTTP_BODY".freeze
 
     # This is the initial part that your handler is identified as by URIClassifier.
     SCRIPT_NAME="SCRIPT_NAME".freeze
@@ -64,8 +67,8 @@ module Mongrel
     # The original URI requested by the client.  Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
     REQUEST_URI='REQUEST_URI'.freeze
     REQUEST_PATH='REQUEST_PATH'.freeze
-
-    MONGREL_VERSION="1.2.0".freeze
+    
+    MONGREL_VERSION="2.0".freeze
 
     MONGREL_TMP_BASE="mongrel".freeze
 
diff --git a/lib/mongrel/http_request.rb b/lib/mongrel/http_request.rb
index 70f236f..f76e0bb 100644
--- a/lib/mongrel/http_request.rb
+++ b/lib/mongrel/http_request.rb
@@ -6,22 +6,24 @@ module Mongrel
   # a StringIO object.  To be safe, you should assume it works like a file.
   #
   class HttpRequest
-    attr_reader :body, :params
+    attr_reader :body, :params, :logger
 
     # You don't really call this.  It's made for you.
     # Main thing it does is hook up the params, and store any remaining
     # body data into the HttpRequest.body attribute.
-    def initialize(params, socket)
+    def initialize(params, socket, logger)
       @params = params
       @socket = socket
+      @logger = logger
+      
       content_length = @params[Const::CONTENT_LENGTH].to_i
-      remain = content_length - @params.http_body.length
+      remain = content_length - @params[Const::HTTP_BODY].length
 
       # Some clients (like FF1.0) report 0 for body and then send a body.  This will probably truncate them but at least the request goes through usually.
       if remain <= 0
         # we've got everything, pack it up
         @body = StringIO.new
-        @body.write @params.http_body
+        @body.write @params[Const::HTTP_BODY]
       elsif remain > 0
         # must read more data to complete body
         if remain > Const::MAX_BODY
@@ -33,7 +35,7 @@ module Mongrel
           @body = StringIO.new
         end
 
-        @body.write @params.http_body
+        @body.write @params[Const::HTTP_BODY]
         read_body(remain, content_length)
       end
 
@@ -66,21 +68,20 @@ module Mongrel
     # part of the body that has been read to be in the @body already.
     def read_body(remain, total)
       begin
-        # write the odd sized chunk first
-        @params.http_body = read_socket(remain % Const::CHUNK_SIZE)
+        # Write the odd sized chunk first
+        @params[Const::HTTP_BODY] = read_socket(remain % Const::CHUNK_SIZE)
 
-        remain -= @body.write(@params.http_body)
+        remain -= @body.write(@params[Const::HTTP_BODY])
 
-        # then stream out nothing but perfectly sized chunks
+        # Then stream out nothing but perfectly sized chunks
         until remain <= 0 or @socket.closed?
           # ASSUME: we are writing to a disk and these writes always write the requested amount
-          @params.http_body = read_socket(Const::CHUNK_SIZE)
-          remain -= @body.write(@params.http_body)
+          @params[Const::HTTP_BODY] = read_socket(Const::CHUNK_SIZE)
+          remain -= @body.write(@params[Const::HTTP_BODY])
         end
       rescue Object => e
-        Mongrel.logger.error "#{Time.now}: Error reading HTTP body: #{e.inspect}"
-        Mongrel.logger.error e.backtrace.join("\n")
-        # any errors means we should delete the file, including if the file is dumped
+        logger.error "Error reading HTTP body: #{e.inspect}"
+        # Any errors means we should delete the file, including if the file is dumped
         @socket.close rescue nil
         @body.close! if @body.class == Tempfile
         @body = nil # signals that there was a problem
@@ -101,44 +102,5 @@ module Mongrel
         raise "Socket already closed when reading."
       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).
-    def self.escape(s)
-      s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
-        '%'+$1.unpack('H2'*$1.size).join('%').upcase
-      }.tr(' ', '+')
-    end
-
-
-    # Unescapes a URI escaped string. (Stolen from Camping).
-    def self.unescape(s)
-      s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
-        [$1.delete('%')].pack('H*')
-      }
-    end
-
-    # Parses a query string by breaking it up at the '&'
-    # and ';' characters.  You can also use this to parse
-    # cookies by changing the characters used in the second
-    # parameter (which defaults to '&;'.
-    def self.query_parse(qs, d = '&;')
-      params = {}
-      (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
-        k, v=unescape(p).split('=',2)
-        if cur = params[k]
-          if cur.class == Array
-            params[k] << v
-          else
-            params[k] = [cur, v]
-          end
-        else
-          params[k] = v
-        end
-      }
-
-      return params
-    end
   end
 end
diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb
index c89cd0d..91482cd 100644
--- a/test/unit/test_http_parser.rb
+++ b/test/unit/test_http_parser.rb
@@ -157,22 +157,5 @@ class HttpParserTest < Test::Unit::TestCase
     end
 
   end
-
-
-
-  def test_query_parse
-    res = HttpRequest.query_parse("zed=1&frank=#{HttpRequest.escape('&&& ')}")
-    assert res["zed"], "didn't get the request right"
-    assert res["frank"], "no frank"
-    assert_equal "1", res["zed"], "wrong result"
-    assert_equal "&&& ", HttpRequest.unescape(res["frank"]), "wrong result"
-
-    res = HttpRequest.query_parse("zed=1&zed=2&zed=3&frank=11;zed=45")
-    assert res["zed"], "didn't get the request right"
-    assert res["frank"], "no frank"
-    assert_equal 4,res["zed"].length, "wrong number for zed"
-    assert_equal "11",res["frank"], "wrong number for frank"
-  end
-  
 end