about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--Manifest12
-rw-r--r--README2
-rwxr-xr-xbin/unicorn_rails42
-rw-r--r--ext/unicorn/http11/http11.c123
-rw-r--r--ext/unicorn/http11/http11_parser.c47
-rw-r--r--ext/unicorn/http11/http11_parser.h4
-rw-r--r--ext/unicorn/http11/http11_parser.rl3
-rw-r--r--lib/unicorn.rb2
-rw-r--r--lib/unicorn/app/old_rails.rb23
-rw-r--r--lib/unicorn/app/old_rails/static.rb58
-rw-r--r--lib/unicorn/cgi_wrapper.rb139
-rw-r--r--lib/unicorn/const.rb34
-rw-r--r--lib/unicorn/http_request.rb118
-rw-r--r--lib/unicorn/http_response.rb6
-rw-r--r--lib/unicorn/socket.rb17
-rw-r--r--test/benchmark/README55
-rw-r--r--test/benchmark/big_request.rb35
-rw-r--r--test/benchmark/dd.ru18
-rw-r--r--test/benchmark/previous.rb11
-rw-r--r--test/benchmark/request.rb47
-rw-r--r--test/benchmark/response.rb29
-rw-r--r--test/benchmark/simple.rb11
-rw-r--r--test/benchmark/utils.rb82
-rw-r--r--test/unit/test_http_parser.rb113
-rw-r--r--test/unit/test_socket_helper.rb159
25 files changed, 820 insertions, 370 deletions
diff --git a/Manifest b/Manifest
index 0889fc7..3487b13 100644
--- a/Manifest
+++ b/Manifest
@@ -21,6 +21,9 @@ ext/unicorn/http11/http11_parser.rl
 ext/unicorn/http11/http11_parser_common.rl
 lib/unicorn.rb
 lib/unicorn/app/exec_cgi.rb
+lib/unicorn/app/old_rails.rb
+lib/unicorn/app/old_rails/static.rb
+lib/unicorn/cgi_wrapper.rb
 lib/unicorn/configurator.rb
 lib/unicorn/const.rb
 lib/unicorn/http_request.rb
@@ -30,9 +33,11 @@ lib/unicorn/socket.rb
 lib/unicorn/util.rb
 setup.rb
 test/aggregate.rb
-test/benchmark/previous.rb
-test/benchmark/simple.rb
-test/benchmark/utils.rb
+test/benchmark/README
+test/benchmark/big_request.rb
+test/benchmark/dd.ru
+test/benchmark/request.rb
+test/benchmark/response.rb
 test/exec/README
 test/exec/test_exec.rb
 test/test_helper.rb
@@ -42,4 +47,5 @@ test/unit/test_http_parser.rb
 test/unit/test_request.rb
 test/unit/test_response.rb
 test/unit/test_server.rb
+test/unit/test_socket_helper.rb
 test/unit/test_upload.rb
diff --git a/README b/README
index 0850f2e..b53d7c6 100644
--- a/README
+++ b/README
@@ -90,7 +90,7 @@ of your application or libraries. However, your Rack application may use
 threads internally (and should even be able to continue running threads
 after the request is complete).
 
-=== Rack-enabled versions of Rails (v2.3.2+)
+=== for Rails applications (should work for all 1.2 or later versions)
 
 In RAILS_ROOT, run:
 
diff --git a/bin/unicorn_rails b/bin/unicorn_rails
index 177c109..fae6f4b 100755
--- a/bin/unicorn_rails
+++ b/bin/unicorn_rails
@@ -7,6 +7,7 @@ rails_pid = File.join(Unicorn::HttpServer::DEFAULT_START_CTX[:cwd],
                       "/tmp/pids/unicorn.pid")
 cmd = File.basename($0)
 daemonize = false
+static = true
 listeners = []
 options = { :listeners => listeners }
 host, port = Unicorn::Const::DEFAULT_HOST, 3000
@@ -123,7 +124,22 @@ rails_loader = lambda do ||
   when nil
     lambda do ||
       require 'config/environment'
-      ActionController::Dispatcher.new
+
+      # it seems Rails >=2.2 support Rack, but only >=2.3 requires it
+      old_rails = case ::Rails::VERSION::MAJOR
+      when 0, 1 then true
+      when 2 then Rails::VERSION::MINOR < 3 ? true : false
+      else
+        false
+      end
+
+      if old_rails
+        require 'rack'
+        require 'unicorn/app/old_rails'
+        Unicorn::App::OldRails.new
+      else
+        ActionController::Dispatcher.new
+      end
     end
   when /\.ru$/
     raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
@@ -147,12 +163,26 @@ app = lambda do ||
   require 'active_support'
   require 'action_controller'
   ActionController::Base.relative_url_root = map_path if map_path
+  map_path ||= '/'
+  inner_app = inner_app.call
   Rack::Builder.new do
-    use Rails::Rack::LogTailer unless daemonize
-    use Rails::Rack::Debugger if $DEBUG
-    map(map_path || '/') do
-      use Rails::Rack::Static
-      run inner_app.call
+    if inner_app.class.to_s == "Unicorn::App::OldRails"
+      $stderr.puts "LogTailer not available for Rails < 2.3" unless daemonize
+      $stderr.puts "Debugger not available" if $DEBUG
+      map(map_path) do
+        if static
+          require 'unicorn/app/old_rails/static'
+          use Unicorn::App::OldRails::Static
+        end
+        run inner_app
+      end
+    else
+      use Rails::Rack::LogTailer unless daemonize
+      use Rails::Rack::Debugger if $DEBUG
+      map(map_path) do
+        use Rails::Rack::Static if static
+        run inner_app
+      end
     end
   end.to_app
 end
diff --git a/ext/unicorn/http11/http11.c b/ext/unicorn/http11/http11.c
index 0b96099..f62dce7 100644
--- a/ext/unicorn/http11/http11.c
+++ b/ext/unicorn/http11/http11.c
@@ -1,4 +1,5 @@
 /**
+ * Copyright (c) 2009 Eric Wong (all bugs are Eric's fault)
  * Copyright (c) 2005 Zed A. Shaw
  * You can redistribute it and/or modify it under the same terms as Ruby.
  */
@@ -367,113 +368,37 @@ static VALUE HttpParser_reset(VALUE self)
 
 /**
  * call-seq:
- *    parser.finish -> true/false
+ *    parser.execute(req_hash, data) -> true/false
  *
- * Finishes a parser early which could put in a "good" or bad state.
- * You should call reset after finish it or bad things will happen.
- */
-static VALUE HttpParser_finish(VALUE self)
-{
-  http_parser *http = NULL;
-  DATA_GET(self, http_parser, http);
-  http_parser_finish(http);
-
-  return http_parser_is_finished(http) ? Qtrue : Qfalse;
-}
-
-
-/**
- * call-seq:
- *    parser.execute(req_hash, data, start) -> Integer
- *
- * Takes a Hash and a String of data, parses the String of data filling in the Hash
- * returning an Integer to indicate how much of the data has been read.  No matter
- * what the return value, you should call HttpParser#finished? and HttpParser#error?
- * to figure out if it's done parsing or there was an error.
+ * Takes a Hash and a String of data, parses the String of data filling
+ * in the Hash returning a boolean to indicate whether or not parsing
+ * is finished.
  *
- * This function now throws an exception when there is a parsing error.  This makes
- * the logic for working with the parser much easier.  You can still test for an
- * error, but now you need to wrap the parser with an exception handling block.
- *
- * The third argument allows for parsing a partial request and then continuing
- * the parsing from that position.  It needs all of the original data as well
- * so you have to append to the data buffer as you read.
+ * This function now throws an exception when there is a parsing error.
+ * This makes the logic for working with the parser much easier.  You
+ * will need to wrap the parser with an exception handling block.
  */
-static VALUE HttpParser_execute(VALUE self, VALUE req_hash,
-                                VALUE data, VALUE start)
-{
-  http_parser *http = NULL;
-  int from = 0;
-  char *dptr = NULL;
-  long dlen = 0;
-
-  DATA_GET(self, http_parser, http);
-
-  from = FIX2INT(start);
-  dptr = RSTRING_PTR(data);
-  dlen = RSTRING_LEN(data);
-
-  if(from >= dlen) {
-    rb_raise(eHttpParserError, "Requested start is after data buffer end.");
-  } else {
-    http->data = (void *)req_hash;
-    http_parser_execute(http, dptr, dlen, from);
-
-    VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
-
-    if(http_parser_has_error(http)) {
-      rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
-    } else {
-      return INT2FIX(http_parser_nread(http));
-    }
-  }
-}
-
 
-
-/**
- * call-seq:
- *    parser.error? -> true/false
- *
- * Tells you whether the parser is in an error state.
- */
-static VALUE HttpParser_has_error(VALUE self)
+static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data)
 {
-  http_parser *http = NULL;
-  DATA_GET(self, http_parser, http);
+  http_parser *http;
+  char *dptr = RSTRING_PTR(data);
+  long dlen = RSTRING_LEN(data);
 
-  return http_parser_has_error(http) ? Qtrue : Qfalse;
-}
-
-
-/**
- * call-seq:
- *    parser.finished? -> true/false
- *
- * Tells you whether the parser is finished or not and in a good state.
- */
-static VALUE HttpParser_is_finished(VALUE self)
-{
-  http_parser *http = NULL;
   DATA_GET(self, http_parser, http);
 
-  return http_parser_is_finished(http) ? Qtrue : Qfalse;
-}
+  if (http->nread < dlen) {
+    http->data = (void *)req_hash;
+    http_parser_execute(http, dptr, dlen);
 
+    VALIDATE_MAX_LENGTH(http->nread, HEADER);
 
-/**
- * call-seq:
- *    parser.nread -> Integer
- *
- * Returns the amount of data processed so far during this processing cycle.  It is
- * set to 0 on initialize or reset calls and is incremented each time execute is called.
- */
-static VALUE HttpParser_nread(VALUE self)
-{
-  http_parser *http = NULL;
-  DATA_GET(self, http_parser, http);
+    if (!http_parser_has_error(http))
+      return http_parser_is_finished(http) ? Qtrue : Qfalse;
 
-  return INT2FIX(http->nread);
+    rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
+  }
+  rb_raise(eHttpParserError, "Requested start is after data buffer end.");
 }
 
 void Init_http11()
@@ -504,10 +429,6 @@ void Init_http11()
   rb_define_alloc_func(cHttpParser, HttpParser_alloc);
   rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
   rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
-  rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
-  rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
-  rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
-  rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
-  rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
+  rb_define_method(cHttpParser, "execute", HttpParser_execute,2);
   init_common_fields();
 }
diff --git a/ext/unicorn/http11/http11_parser.c b/ext/unicorn/http11/http11_parser.c
index d33eed0..b6d55c8 100644
--- a/ext/unicorn/http11/http11_parser.c
+++ b/ext/unicorn/http11/http11_parser.c
@@ -63,9 +63,10 @@ int http_parser_init(http_parser *parser)  {
 
 
 /** exec **/
-size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
+size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len)  {
   const char *p, *pe;
   int cs = parser->cs;
+  size_t off = parser->nread;
 
   assert(off <= len && "offset past end of buffer");
 
@@ -76,7 +77,7 @@ size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len,
   assert(pe - p == len - off && "pointers aren't same distance");
 
   
-#line 80 "http11_parser.c"
+#line 81 "http11_parser.c"
         {
         if ( p == pe )
                 goto _test_eof;
@@ -107,7 +108,7 @@ st2:
         if ( ++p == pe )
                 goto _test_eof2;
 case 2:
-#line 111 "http11_parser.c"
+#line 112 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr2;
                 case 36: goto st38;
@@ -133,7 +134,7 @@ st3:
         if ( ++p == pe )
                 goto _test_eof3;
 case 3:
-#line 137 "http11_parser.c"
+#line 138 "http11_parser.c"
         switch( (*p) ) {
                 case 42: goto tr4;
                 case 43: goto tr5;
@@ -157,7 +158,7 @@ st4:
         if ( ++p == pe )
                 goto _test_eof4;
 case 4:
-#line 161 "http11_parser.c"
+#line 162 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr8;
                 case 35: goto tr9;
@@ -228,7 +229,7 @@ st5:
         if ( ++p == pe )
                 goto _test_eof5;
 case 5:
-#line 232 "http11_parser.c"
+#line 233 "http11_parser.c"
         if ( (*p) == 72 )
                 goto tr10;
         goto st0;
@@ -240,7 +241,7 @@ st6:
         if ( ++p == pe )
                 goto _test_eof6;
 case 6:
-#line 244 "http11_parser.c"
+#line 245 "http11_parser.c"
         if ( (*p) == 84 )
                 goto st7;
         goto st0;
@@ -326,7 +327,7 @@ st14:
         if ( ++p == pe )
                 goto _test_eof14;
 case 14:
-#line 330 "http11_parser.c"
+#line 331 "http11_parser.c"
         if ( (*p) == 10 )
                 goto st15;
         goto st0;
@@ -378,7 +379,7 @@ st57:
         if ( ++p == pe )
                 goto _test_eof57;
 case 57:
-#line 382 "http11_parser.c"
+#line 383 "http11_parser.c"
         goto st0;
 tr21:
 #line 37 "http11_parser.rl"
@@ -394,7 +395,7 @@ st17:
         if ( ++p == pe )
                 goto _test_eof17;
 case 17:
-#line 398 "http11_parser.c"
+#line 399 "http11_parser.c"
         switch( (*p) ) {
                 case 33: goto tr23;
                 case 58: goto tr24;
@@ -433,7 +434,7 @@ st18:
         if ( ++p == pe )
                 goto _test_eof18;
 case 18:
-#line 437 "http11_parser.c"
+#line 438 "http11_parser.c"
         switch( (*p) ) {
                 case 13: goto tr26;
                 case 32: goto tr27;
@@ -447,7 +448,7 @@ st19:
         if ( ++p == pe )
                 goto _test_eof19;
 case 19:
-#line 451 "http11_parser.c"
+#line 452 "http11_parser.c"
         if ( (*p) == 13 )
                 goto tr29;
         goto st19;
@@ -500,7 +501,7 @@ st20:
         if ( ++p == pe )
                 goto _test_eof20;
 case 20:
-#line 504 "http11_parser.c"
+#line 505 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr31;
                 case 35: goto st0;
@@ -518,7 +519,7 @@ st21:
         if ( ++p == pe )
                 goto _test_eof21;
 case 21:
-#line 522 "http11_parser.c"
+#line 523 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr34;
                 case 35: goto st0;
@@ -536,7 +537,7 @@ st22:
         if ( ++p == pe )
                 goto _test_eof22;
 case 22:
-#line 540 "http11_parser.c"
+#line 541 "http11_parser.c"
         if ( (*p) < 65 ) {
                 if ( 48 <= (*p) && (*p) <= 57 )
                         goto st23;
@@ -567,7 +568,7 @@ st24:
         if ( ++p == pe )
                 goto _test_eof24;
 case 24:
-#line 571 "http11_parser.c"
+#line 572 "http11_parser.c"
         switch( (*p) ) {
                 case 43: goto st24;
                 case 58: goto st25;
@@ -592,7 +593,7 @@ st25:
         if ( ++p == pe )
                 goto _test_eof25;
 case 25:
-#line 596 "http11_parser.c"
+#line 597 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr8;
                 case 35: goto tr9;
@@ -636,7 +637,7 @@ st28:
         if ( ++p == pe )
                 goto _test_eof28;
 case 28:
-#line 640 "http11_parser.c"
+#line 641 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr42;
                 case 35: goto tr43;
@@ -685,7 +686,7 @@ st31:
         if ( ++p == pe )
                 goto _test_eof31;
 case 31:
-#line 689 "http11_parser.c"
+#line 690 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr8;
                 case 35: goto tr9;
@@ -733,7 +734,7 @@ st34:
         if ( ++p == pe )
                 goto _test_eof34;
 case 34:
-#line 737 "http11_parser.c"
+#line 738 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr53;
                 case 35: goto tr54;
@@ -751,7 +752,7 @@ st35:
         if ( ++p == pe )
                 goto _test_eof35;
 case 35:
-#line 755 "http11_parser.c"
+#line 756 "http11_parser.c"
         switch( (*p) ) {
                 case 32: goto tr57;
                 case 35: goto tr58;
@@ -769,7 +770,7 @@ st36:
         if ( ++p == pe )
                 goto _test_eof36;
 case 36:
-#line 773 "http11_parser.c"
+#line 774 "http11_parser.c"
         if ( (*p) < 65 ) {
                 if ( 48 <= (*p) && (*p) <= 57 )
                         goto st37;
@@ -1184,7 +1185,7 @@ case 56:
         _test_eof: {}
         _out: {}
         }
-#line 121 "http11_parser.rl"
+#line 122 "http11_parser.rl"
 
   if (!http_parser_has_error(parser))
     parser->cs = cs;
diff --git a/ext/unicorn/http11/http11_parser.h b/ext/unicorn/http11/http11_parser.h
index c96b3a0..6c332fe 100644
--- a/ext/unicorn/http11/http11_parser.h
+++ b/ext/unicorn/http11/http11_parser.h
@@ -36,10 +36,8 @@ typedef struct http_parser {
 
 int http_parser_init(http_parser *parser);
 int http_parser_finish(http_parser *parser);
-size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
+size_t http_parser_execute(http_parser *parser, const char *data, size_t len);
 int http_parser_has_error(http_parser *parser);
 int http_parser_is_finished(http_parser *parser);
 
-#define http_parser_nread(parser) (parser)->nread
-
 #endif
diff --git a/ext/unicorn/http11/http11_parser.rl b/ext/unicorn/http11/http11_parser.rl
index c3c4b1f..1fad2ca 100644
--- a/ext/unicorn/http11/http11_parser.rl
+++ b/ext/unicorn/http11/http11_parser.rl
@@ -105,9 +105,10 @@ int http_parser_init(http_parser *parser)  {
 
 
 /** exec **/
-size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off)  {
+size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len)  {
   const char *p, *pe;
   int cs = parser->cs;
+  size_t off = parser->nread;
 
   assert(off <= len && "offset past end of buffer");
 
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index eefbfc1..e36cb1e 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -135,7 +135,7 @@ module Unicorn
     def listen(address)
       return if String === address && listener_names.include?(address)
 
-      if io = bind_listen(address, @backlog)
+      if io = bind_listen(address, { :backlog => @backlog })
         if Socket == io.class
           @io_purgatory << io
           io = server_cast(io)
diff --git a/lib/unicorn/app/old_rails.rb b/lib/unicorn/app/old_rails.rb
new file mode 100644
index 0000000..bb9577a
--- /dev/null
+++ b/lib/unicorn/app/old_rails.rb
@@ -0,0 +1,23 @@
+# This code is based on the original Rails handler in Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+# Additional work donated by contributors.  See CONTRIBUTORS for more info.
+require 'unicorn/cgi_wrapper'
+require 'dispatcher'
+
+module Unicorn; module App; end; end
+
+# Implements a handler that can run Rails.
+class Unicorn::App::OldRails
+
+  def call(env)
+    cgi = Unicorn::CGIWrapper.new(env)
+    Dispatcher.dispatch(cgi,
+        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
+        cgi.body)
+    cgi.out  # finalize the response
+    cgi.rack_response
+  end
+
+end
diff --git a/lib/unicorn/app/old_rails/static.rb b/lib/unicorn/app/old_rails/static.rb
new file mode 100644
index 0000000..c9366d2
--- /dev/null
+++ b/lib/unicorn/app/old_rails/static.rb
@@ -0,0 +1,58 @@
+# This code is based on the original Rails handler in Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+
+require 'rack/file'
+
+# Static file handler for Rails < 2.3.  This handler is only provided
+# as a convenience for developers.  Performance-minded deployments should
+# use nginx (or similar) for serving static files.
+#
+# This 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+rest_operator+".html" exists
+#   then serve that.
+#
+# This means that if you are using page caching it will actually work
+# with Unicorn and you should see a decent speed boost (but not as
+# fast as if you use a static server like nginx).
+class Unicorn::App::OldRails::Static
+  FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
+
+  def initialize(app)
+    @app = app
+    @root = "#{::RAILS_ROOT}/public"
+    @file_server = ::Rack::File.new(@root)
+  end
+
+  def call(env)
+    # short circuit this ASAP if serving non-file methods
+    FILE_METHODS.include?(env[Unicorn::Const::REQUEST_METHOD]) or
+      return @app.call(env)
+
+    # first try the path as-is
+    path_info = env[Unicorn::Const::PATH_INFO].chomp("/")
+    if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
+      # File exists as-is so serve it up
+      env[Unicorn::Const::PATH_INFO] = path_info
+      return @file_server.call(env)
+    end
+
+    # then try the cached version:
+
+    # grab the semi-colon REST operator used by old versions of Rails
+    # this is the reason we didn't just copy the new Rails::Rack::Static
+    env[Unicorn::Const::REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
+    path_info << "#$1#{ActionController::Base.page_cache_extension}"
+
+    if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
+      env[Unicorn::Const::PATH_INFO] = path_info
+      return @file_server.call(env)
+    end
+
+    @app.call(env) # call OldRails
+  end
+end if defined?(Unicorn::App::OldRails)
diff --git a/lib/unicorn/cgi_wrapper.rb b/lib/unicorn/cgi_wrapper.rb
new file mode 100644
index 0000000..816b0a0
--- /dev/null
+++ b/lib/unicorn/cgi_wrapper.rb
@@ -0,0 +1,139 @@
+# This code is based on the original CGIWrapper from Mongrel
+# Copyright (c) 2005 Zed A. Shaw
+# Copyright (c) 2009 Eric Wong
+# You can redistribute it and/or modify it under the same terms as Ruby.
+#
+# Additional work donated by contributors.  See CONTRIBUTORS for more info.
+
+require 'cgi'
+
+module Unicorn; end
+
+# The beginning of a complete wrapper around Unicorn'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 us if you see special
+# warnings.  This work is still very alpha so we need testers to help
+# work out the various corner cases.
+class Unicorn::CGIWrapper < ::CGI
+  undef_method :env_table
+  attr_reader :env_table
+  attr_reader :body
+
+  # these are stripped out of any keys passed to CGIWrapper.header function
+  NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
+  CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
+  CHARSET = 'charset'.freeze # this gets appended to Content-Type
+  COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
+  STATUS = 'status'.freeze # stored as @status
+
+  # some of these are common strings, but this is the only module
+  # using them and the reason they're not in Unicorn::Const
+  SET_COOKIE = 'Set-Cookie'.freeze
+  CONTENT_TYPE = 'Content-Type'.freeze
+  CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
+  RACK_INPUT = 'rack.input'.freeze
+  RACK_ERRORS = 'rack.errors'.freeze
+
+  # this maps CGI header names to HTTP header names
+  HEADER_MAP = {
+    'type' => CONTENT_TYPE,
+    'server' => 'Server'.freeze,
+    'language' => 'Content-Language'.freeze,
+    'expires' => 'Expires'.freeze,
+    'length' => CONTENT_LENGTH,
+  }.freeze
+
+  # Takes an a Rackable environment, plus any additional CGI.new
+  # arguments These are used internally to create a wrapper around the
+  # real CGI while maintaining Rack/Unicorn's view of the world.  This
+  # this will NOT deal well with large responses that take up a lot of
+  # memory, but neither does the CGI nor the original CGIWrapper from
+  # Mongrel...
+  def initialize(rack_env, *args)
+    @env_table = rack_env
+    @status = 200
+    @head = { :cookies => [] }
+    @body = StringIO.new
+    super(*args)
+  end
+
+  # finalizes the response in a way Rack applications would expect
+  def rack_response
+    cookies = @head.delete(:cookies)
+    cookies.empty? or @head[SET_COOKIE] = cookies.join("\n")
+    @head[CONTENT_LENGTH] ||= @body.size
+
+    [ @status, @head, [ @body.string ] ]
+  end
+
+  # The header is typically called to send back the header.  In our case we
+  # collect it into a hash for later usage.  This can be called multiple
+  # times to set different cookies.
+  def header(options = "text/html")
+    # if they pass in a string then just write the Content-Type
+    if String === options
+      @head[CONTENT_TYPE] ||= options
+    else
+      HEADER_MAP.each_pair do |from, to|
+        from = options.delete(from) or next
+        @head[to] = from
+      end
+
+      @head[CONTENT_TYPE] ||= "text/html"
+      if charset = options.delete(CHARSET)
+        @head[CONTENT_TYPE] << "; charset=#{charset}"
+      end
+
+      # lots of ways to set cookies
+      if cookie = options.delete(COOKIE)
+        cookies = @head[:cookies]
+        case cookie
+        when Array
+          cookie.each { |c| cookies << c.to_s }
+        when Hash
+          cookie.each_value { |c| cookies << c.to_s }
+        else
+          cookies << cookie.to_s
+        end
+      end
+      @status ||= (status = options.delete(STATUS))
+      # drop the keys we don't want anymore
+      options.delete(NPH)
+      options.delete(CONNECTION)
+
+      # finally, set the rest of the headers as-is
+      options.each_pair { |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
+
+  # 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.  This will have no effect if called
+  # the second time if the first "outputted" anything.
+  def out(options = "text/html")
+    header(options)
+    @body.size == 0 or return
+    @body << yield
+  end
+
+  # Used to wrap the normal stdinput variable used inside CGI.
+  def stdinput
+    @env_table[RACK_INPUT]
+  end
+
+  # The stdoutput should be completely bypassed but we'll drop a
+  # warning just in case
+  def stdoutput
+    err = @env_table[RACK_ERRORS]
+    err.puts "WARNING: Your program is doing something not expected."
+    err.puts "Please tell Eric that stdoutput was used and what software " \
+             "you are running.  Thanks."
+    @body
+  end
+
+end
diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb
index ed7f5b1..4e78171 100644
--- a/lib/unicorn/const.rb
+++ b/lib/unicorn/const.rb
@@ -48,10 +48,6 @@ module Unicorn
   # the constant just refers to a string with the same contents.  Using these constants
   # gave about a 3% to 10% performance improvement over using the strings directly.
   # Symbols did not really improve things much compared to constants.
-  #
-  # While Unicorn does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
-  # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
-  # too taxing on performance.
   module Const
     DATE="Date".freeze
 
@@ -61,10 +57,7 @@ module Unicorn
     # 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
-
-    # The original URI requested by the client.  Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
+    # The original URI requested by the client.
     REQUEST_URI='REQUEST_URI'.freeze
     REQUEST_PATH='REQUEST_PATH'.freeze
     
@@ -76,14 +69,6 @@ module Unicorn
     DEFAULT_PORT = "8080".freeze    # default TCP listen port
     DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}".freeze
 
-    # The standard empty 404 response for bad requests.  Use Error4040Handler for custom stuff.
-    ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Unicorn #{UNICORN_VERSION}\r\n\r\nNOT FOUND".freeze
-
-    CONTENT_LENGTH="CONTENT_LENGTH".freeze
-
-    # A common header for indicating the server is too busy.  Not used yet.
-    ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
-
     # The basic max request size we'll try to read.
     CHUNK_SIZE=(16 * 1024)
 
@@ -95,22 +80,11 @@ module Unicorn
     MAX_BODY=MAX_HEADER
 
     # A frozen format for this is about 15% faster
-    CONTENT_TYPE = "Content-Type".freeze
-    LAST_MODIFIED = "Last-Modified".freeze
-    ETAG = "ETag".freeze
-    REQUEST_METHOD="REQUEST_METHOD".freeze
-    GET="GET".freeze
-    HEAD="HEAD".freeze
-    # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
-    ETAG_FORMAT="\"%x-%x-%x\"".freeze
-    LINE_END="\r\n".freeze
+    CONTENT_LENGTH="CONTENT_LENGTH".freeze
     REMOTE_ADDR="REMOTE_ADDR".freeze
     HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
-    HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze
-    HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze
-    REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
-    HOST = "HOST".freeze
-    CONNECTION = "Connection".freeze
+    QUERY_STRING="QUERY_STRING".freeze
+    RACK_INPUT="rack.input".freeze
   end
 
 end
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index ee407ab..7106f62 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -13,6 +13,20 @@ module Unicorn
   #
   class HttpRequest
 
+     # default parameters we merge into the request env for Rack handlers
+     DEF_PARAMS = {
+       "rack.errors" => $stderr,
+       "rack.multiprocess" => true,
+       "rack.multithread" => false,
+       "rack.run_once" => false,
+       "rack.url_scheme" => "http",
+       "rack.version" => [0, 1],
+       "SCRIPT_NAME" => "",
+
+       # this is not in the Rack spec, but some apps may rely on it
+       "SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
+     }.freeze
+
     def initialize(logger)
       @logger = logger
       @body = nil
@@ -29,59 +43,39 @@ module Unicorn
       @body = nil
     end
 
-    #
     # Does the majority of the IO processing.  It has been written in
-    # Ruby using about 7 different IO processing strategies and no
-    # matter how it's done the performance just does not improve.  It is
-    # currently carefully constructed to make sure that it gets the best
-    # possible performance, but anyone who thinks they can make it
-    # faster is more than welcome to take a crack at it.
+    # Ruby using about 8 different IO processing strategies.
+    #
+    # It is currently carefully constructed to make sure that it gets
+    # the best possible performance for the common case: GET requests
+    # that are fully complete after a single read(2)
+    #
+    # Anyone who thinks they can make it faster is more than welcome to
+    # take a crack at it.
     #
     # returns an environment hash suitable for Rack if successful
     # This does minimal exception trapping and it is up to the caller
     # to handle any socket errors (e.g. user aborted upload).
     def read(socket)
-      data = String.new(read_socket(socket))
-      nparsed = 0
-
-      # Assumption: nparsed will always be less since data will get
-      # filled with more after each parsing.  If it doesn't get more
-      # then there was a problem with the read operation on the client
-      # socket.  Effect is to stop processing when the socket can't
-      # fill the buffer for further parsing.
-      while nparsed < data.length
-        nparsed = @parser.execute(@params, data, nparsed)
-
-        if @parser.finished?
-          # From http://www.ietf.org/rfc/rfc3875:
-          # "Script authors should be aware that the REMOTE_ADDR and
-          #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
-          #  may not identify the ultimate source of the request.  They
-          #  identify the client for the immediate request to the server;
-          #  that client may be a proxy, gateway, or other intermediary
-          #  acting on behalf of the actual source client."
-          @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
-
-          handle_body(socket) and return rack_env # success!
-          return nil # fail
-        else
-          # Parser is not done, queue up more data to read and continue
-          # parsing
-          data << read_socket(socket)
-          if data.length >= Const::MAX_HEADER
-            raise HttpParserError.new("HEADER is longer than allowed, " \
-                                      "aborting client early.")
-          end
-        end
+      # short circuit the common case with small GET requests first
+      @parser.execute(@params, read_socket(socket)) and
+          return handle_body(socket)
+
+      data = @buffer.dup # read_socket will clobber @buffer
+
+      # Parser is not done, queue up more data to read and continue parsing
+      # an Exception thrown from the @parser will throw us out of the loop
+      loop do
+        data << read_socket(socket)
+        @parser.execute(@params, data) and
+            return handle_body(socket)
       end
-      nil # XXX bug?
       rescue HttpParserError => e
         @logger.error "HTTP parse error, malformed request " \
                       "(#{@params[Const::HTTP_X_FORWARDED_FOR] ||
                           socket.unicorn_peeraddr}): #{e.inspect}"
         @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
                       "PARAMS: #{@params.inspect}\n---\n"
-        socket.closed? or socket.close rescue nil
         nil
     end
 
@@ -109,7 +103,7 @@ module Unicorn
       # This will probably truncate them but at least the request goes through
       # usually.
       if remain > 0
-        read_body(socket, remain) or return false # fail!
+        read_body(socket, remain) or return nil # fail!
       end
       @body.rewind
       @body.sysseek(0) if @body.respond_to?(:sysseek)
@@ -118,29 +112,37 @@ module Unicorn
       # another request, we'll truncate it.  Again, we don't do pipelining
       # or keepalive
       @body.truncate(content_length)
-      true
+      rack_env(socket)
     end
 
     # Returns an environment which is rackable:
     # http://rack.rubyforge.org/doc/files/SPEC.html
     # Based on Rack's old Mongrel handler.
-    def rack_env
+    def rack_env(socket)
+      # I'm considering enabling "unicorn.client".  It gives
+      # applications some rope to do some "interesting" things like
+      # replacing a worker with another process that has full control
+      # over the HTTP response.
+      # @params["unicorn.client"] = socket
+
+      # From http://www.ietf.org/rfc/rfc3875:
+      # "Script authors should be aware that the REMOTE_ADDR and
+      #  REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
+      #  may not identify the ultimate source of the request.  They
+      #  identify the client for the immediate request to the server;
+      #  that client may be a proxy, gateway, or other intermediary
+      #  acting on behalf of the actual source client."
+      @params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
+
       # It might be a dumbass full host request header
-      @params[Const::REQUEST_PATH] ||=
-                           URI.parse(@params[Const::REQUEST_URI]).path
-      raise "No REQUEST PATH" unless @params[Const::REQUEST_PATH]
-
-      @params["QUERY_STRING"] ||= ''
-      @params.update({ "rack.version" => [0,1],
-                      "rack.input" => @body,
-                      "rack.errors" => $stderr,
-                      "rack.multithread" => false,
-                      "rack.multiprocess" => true,
-                      "rack.run_once" => false,
-                      "rack.url_scheme" => "http",
-                      Const::PATH_INFO => @params[Const::REQUEST_PATH],
-                      Const::SCRIPT_NAME => "",
-                    })
+      @params[Const::PATH_INFO] = (
+          @params[Const::REQUEST_PATH] ||=
+              URI.parse(@params[Const::REQUEST_URI]).path) or
+         raise "No REQUEST_PATH"
+
+      @params[Const::QUERY_STRING] ||= ''
+      @params[Const::RACK_INPUT] = @body
+      @params.update(DEF_PARAMS)
     end
 
     # Does the heavy lifting of properly reading the larger body requests in
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index c8aa3f9..f928baa 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -35,7 +35,11 @@ module Unicorn
       # the time anyways so just hope our app knows what it's doing
       headers.each do |key, value|
         next if SKIP.include?(key.downcase)
-        value.split(/\n/).each { |v| out << "#{key}: #{v}" }
+        if value =~ /\n/
+          value.split(/\n/).each { |v| out << "#{key}: #{v}" }
+        else
+          out << "#{key}: #{value}"
+        end
       end
 
       # Rack should enforce Content-Length or chunked transfer encoding,
diff --git a/lib/unicorn/socket.rb b/lib/unicorn/socket.rb
index 9519448..4870133 100644
--- a/lib/unicorn/socket.rb
+++ b/lib/unicorn/socket.rb
@@ -62,10 +62,17 @@ module Unicorn
       end
     end
 
+    def log_buffer_sizes(sock, pfx = '')
+      respond_to?(:logger) or return
+      rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
+      sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
+      logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
+    end
+
     # creates a new server, socket. address may be a HOST:PORT or
     # an absolute path to a UNIX socket.  address can even be a Socket
     # object in which case it is immediately returned
-    def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
+    def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 })
       return address unless String === address
 
       domain, bind_addr = if address[0..0] == "/"
@@ -95,7 +102,13 @@ module Unicorn
         sock.close rescue nil
         return nil
       end
-      sock.listen(backlog)
+      if opt[:rcvbuf] || opt[:sndbuf]
+        log_buffer_sizes(sock, "before: ")
+        sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
+        sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
+        log_buffer_sizes(sock, " after: ")
+      end
+      sock.listen(opt[:backlog] || 1024)
       set_server_sockopt(sock) if domain == AF_INET
       sock
     end
diff --git a/test/benchmark/README b/test/benchmark/README
new file mode 100644
index 0000000..b63b8a3
--- /dev/null
+++ b/test/benchmark/README
@@ -0,0 +1,55 @@
+= Performance
+
+Unicorn is pretty fast, and we want it to get faster.  Unicorn strives
+to get HTTP requests to your application and write HTTP responses back
+as quickly as possible.  Unicorn does not do any background processing
+while your app runs, so your app will get all the CPU time provided to
+it by your OS kernel.
+
+A gentle reminder: Unicorn is NOT for serving clients over slow network
+connections.  Use nginx (or something similar) to complement Unicorn if
+you have slow clients.
+
+== dd.ru
+
+This is a pure I/O benchmark.  In the context of Unicorn, this is the
+only one that matters.  It is a standard rackup-compatible .ru file and
+may be used with other Rack-compatible servers.
+
+  unicorn -E none dd.ru
+
+You can change the size and number of chunks in the response with
+the "bs" and "count" environment variables.   The following command
+will cause dd.ru to return 4 chunks of 16384 bytes each, leading to
+65536 byte response:
+
+  bs=16384 count=4 unicorn -E none dd.ru
+
+Or if you want to add logging (small performance impact):
+
+  unicorn -E deployment dd.ru
+
+Eric runs then runs clients on a LAN it in several different ways:
+
+  client@host1 -> unicorn@host1(tcp)
+  client@host2 -> unicorn@host1(tcp)
+  client@host3 -> nginx@host1 -> unicorn@host1(tcp)
+  client@host3 -> nginx@host1 -> unicorn@host1(unix)
+  client@host3 -> nginx@host2 -> unicorn@host1(tcp)
+
+The benchmark client is usually httperf.
+
+Another gentle reminder: performance with slow networks/clients
+is NOT our problem.  That is the job of nginx (or similar).
+
+== request.rb, response.rb, big_request.rb
+
+These are micro-benchmarks designed to test internal components
+of Unicorn.  It assumes the internal Unicorn API is mostly stable.
+
+== Contributors
+
+This directory is maintained independently in the "benchmark" branch
+based against v0.1.0.  Only changes to this directory (test/benchmarks)
+are committed to this branch although the master branch may merge this
+branch occassionaly.
diff --git a/test/benchmark/big_request.rb b/test/benchmark/big_request.rb
new file mode 100644
index 0000000..5f2111b
--- /dev/null
+++ b/test/benchmark/big_request.rb
@@ -0,0 +1,35 @@
+require 'benchmark'
+require 'tempfile'
+require 'unicorn'
+nr = ENV['nr'] ? ENV['nr'].to_i : 100
+bs = ENV['bs'] ? ENV['bs'].to_i : (1024 * 1024)
+count = ENV['count'] ? ENV['count'].to_i : 4
+length = bs * count
+slice = (' ' * bs).freeze
+
+big = Tempfile.new('')
+def big.unicorn_peeraddr; '127.0.0.1'; end
+big.syswrite(
+"PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
+"Host: localhost\r\n" \
+"Accept: */*\r\n" \
+"Content-Length: #{length}\r\n" \
+"User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda\r\n" \
+"\r\n")
+count.times { big.syswrite(slice) }
+big.sysseek(0)
+big.fsync
+
+include Unicorn
+request = HttpRequest.new(Logger.new($stderr))
+
+Benchmark.bmbm do |x|
+  x.report("big") do
+    for i in 1..nr
+      request.read(big)
+      request.reset
+      big.sysseek(0)
+    end
+  end
+end
+
diff --git a/test/benchmark/dd.ru b/test/benchmark/dd.ru
new file mode 100644
index 0000000..111fa2e
--- /dev/null
+++ b/test/benchmark/dd.ru
@@ -0,0 +1,18 @@
+# This benchmark is the simplest test of the I/O facilities in
+# unicorn.  It is meant to return a fixed-sized blob to test
+# the performance of things in Unicorn, _NOT_ the app.
+#
+# Adjusting this benchmark is done via the "bs" (byte size) and "count"
+# environment variables.  "count" designates the count of elements of
+# "bs" length in the Rack response body.  The defaults are bs=4096, count=1
+# to return one 4096-byte chunk.
+bs = ENV['bs'] ? ENV['bs'].to_i : 4096
+count = ENV['count'] ? ENV['count'].to_i : 1
+slice = (' ' * bs).freeze
+body = (1..count).map { slice }.freeze
+hdr = {
+  'Content-Length' => (bs * count).to_s.freeze,
+  'Content-Type' => 'text/plain'.freeze
+}.freeze
+response = [ 200, hdr, body ].freeze
+run(lambda { |env| response })
diff --git a/test/benchmark/previous.rb b/test/benchmark/previous.rb
deleted file mode 100644
index 8b6182a..0000000
--- a/test/benchmark/previous.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Benchmark to compare Mongrel performance against
-# previous Mongrel version (the one installed as a gem).
-#
-# Run with:
-#
-#  ruby previous.rb [num of request]
-#
-
-require File.dirname(__FILE__) + '/utils'
-
-benchmark "print", %w(current gem), 1000, [1, 10, 100]
diff --git a/test/benchmark/request.rb b/test/benchmark/request.rb
new file mode 100644
index 0000000..67266cb
--- /dev/null
+++ b/test/benchmark/request.rb
@@ -0,0 +1,47 @@
+require 'benchmark'
+require 'unicorn'
+nr = ENV['nr'] ? ENV['nr'].to_i : 100000
+
+class TestClient
+  def initialize(response)
+    @response = (response.join("\r\n") << "\r\n\r\n").freeze
+  end
+  def sysread(len, buf)
+    buf.replace(@response)
+  end
+
+  def unicorn_peeraddr
+    '127.0.0.1'
+  end
+end
+
+small = TestClient.new([
+  'GET / HTTP/1.0',
+  'Host: localhost',
+  'Accept: */*',
+  'User-Agent: test-user-agent 0.1.0'
+])
+
+medium = TestClient.new([
+  'GET /hello/world/geturl?abcd=efg&hi#anchor HTTP/1.0',
+  'Host: localhost',
+  'Accept: */*',
+  'User-Agent: test-user-agent 0.1.0 (Mozilla compatible) 5.0 asdfadfasda'
+])
+
+include Unicorn
+request = HttpRequest.new(Logger.new($stderr))
+Benchmark.bmbm do |x|
+  x.report("small") do
+    for i in 1..nr
+      request.read(small)
+      request.reset
+    end
+  end
+  x.report("medium") do
+    for i in 1..nr
+      request.read(medium)
+      request.reset
+    end
+  end
+end
diff --git a/test/benchmark/response.rb b/test/benchmark/response.rb
new file mode 100644
index 0000000..0ff0ac2
--- /dev/null
+++ b/test/benchmark/response.rb
@@ -0,0 +1,29 @@
+require 'benchmark'
+require 'unicorn'
+
+class NullWriter
+  def syswrite(buf); buf.size; end
+  def close; end
+end
+
+include Unicorn
+
+socket = NullWriter.new
+bs = ENV['bs'] ? ENV['bs'].to_i : 4096
+count = ENV['count'] ? ENV['count'].to_i : 1
+slice = (' ' * bs).freeze
+body = (1..count).map { slice }.freeze
+hdr = {
+  'Content-Length' => (bs * count).to_s.freeze,
+  'Content-Type' => 'text/plain'.freeze
+}.freeze
+response = [ 200, hdr, body ].freeze
+
+nr = ENV['nr'] ? ENV['nr'].to_i : 100000
+Benchmark.bmbm do |x|
+  x.report do
+    for i in 1..nr
+      HttpResponse.write(socket.dup, response)
+    end
+  end
+end
diff --git a/test/benchmark/simple.rb b/test/benchmark/simple.rb
deleted file mode 100644
index 906f74c..0000000
--- a/test/benchmark/simple.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# Simple benchmark to compare Mongrel performance against
-# other webservers supported by Rack.
-#
-
-require File.dirname(__FILE__) + '/utils'
-
-libs = %w(current gem WEBrick EMongrel Thin)
-libs = ARGV if ARGV.any?
-
-benchmark "print", libs, 1000, [1, 10, 100]
diff --git a/test/benchmark/utils.rb b/test/benchmark/utils.rb
deleted file mode 100644
index feb22c1..0000000
--- a/test/benchmark/utils.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-
-require 'rubygems'
-require 'rack'
-require 'rack/lobster'
-
-def run(handler_name, n=1000, c=1)
-  port = 7000
-  
-  server = fork do
-    [STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
-      
-    case handler_name
-    when 'EMongrel'
-      require 'swiftcore/evented_mongrel'
-      handler_name = 'Mongrel'
-    
-    when 'Thin'
-      require 'thin'
-      hander_name = 'Thin'
-    
-    when 'gem' # Load the current Mongrel gem
-      require 'mongrel'
-      handler_name = 'Mongrel'
-    
-    when 'current' # Load the current Mongrel version under /lib
-      require File.dirname(__FILE__) + '/../lib/mongrel'
-      handler_name = 'Mongrel'
-      
-    end
-    
-    app = Rack::Lobster.new
-    
-    handler = Rack::Handler.const_get(handler_name)
-    handler.run app, :Host => '0.0.0.0', :Port => port
-  end
-
-  sleep 2
-
-  out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:#{port}/ 2> /dev/null`
-
-  Process.kill('SIGKILL', server)
-  Process.wait
-  
-  if requests = out.match(/^Requests.+?(\d+\.\d+)/)
-    requests[1].to_i
-  else
-    0
-  end
-end
-
-def benchmark(type, servers, request, concurrency_levels)
-  send "#{type}_benchmark", servers, request, concurrency_levels
-end
-
-def graph_benchmark(servers, request, concurrency_levels)
-  require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
-  g = Gruff::Area.new
-  g.title = "Server benchmark"
-  
-  servers.each do |server|
-    g.data(server, concurrency_levels.collect { |c| print '.'; run(server, request, c) })
-  end
-  puts
-  
-  g.x_axis_label = 'Concurrency'
-  g.y_axis_label = 'Requests / sec'
-  g.labels = {}
-  concurrency_levels.each_with_index { |c, i| g.labels[i] = c.to_s }
-  
-  g.write('bench.png')
-  `open bench.png`
-end
-
-def print_benchmark(servers, request, concurrency_levels)
-  puts 'server     request   concurrency   req/s'
-  puts '=' * 42
-  concurrency_levels.each do |c|
-    servers.each do |server|
-      puts "#{server.ljust(8)}   #{request}      #{c.to_s.ljust(4)}          #{run(server, request, c)}"
-    end
-  end
-end \ No newline at end of file
diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb
index fc75990..1deeaa2 100644
--- a/test/unit/test_http_parser.rb
+++ b/test/unit/test_http_parser.rb
@@ -14,33 +14,40 @@ class HttpParserTest < Test::Unit::TestCase
     parser = HttpParser.new
     req = {}
     http = "GET / HTTP/1.1\r\n\r\n"
-    nread = parser.execute(req, http, 0)
-
-    assert nread == http.length, "Failed to parse the full HTTP request"
-    assert parser.finished?, "Parser didn't finish"
-    assert !parser.error?, "Parser had error"
-    assert nread == parser.nread, "Number read returned from execute does not match"
+    assert parser.execute(req, http)
 
     assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
     assert_equal '/', req['REQUEST_PATH']
     assert_equal 'HTTP/1.1', req['HTTP_VERSION']
     assert_equal '/', req['REQUEST_URI']
-    assert_equal 'GET', req['REQUEST_METHOD']    
+    assert_equal 'GET', req['REQUEST_METHOD']
     assert_nil req['FRAGMENT']
     assert_nil req['QUERY_STRING']
 
     parser.reset
-    assert parser.nread == 0, "Number read after reset should be 0"
+    req.clear
+
+    assert ! parser.execute(req, "G")
+    assert req.empty?
+
+    # try parsing again to ensure we were reset correctly
+    http = "GET /hello-world HTTP/1.1\r\n\r\n"
+    assert parser.execute(req, http)
+
+    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_equal '/hello-world', req['REQUEST_PATH']
+    assert_equal 'HTTP/1.1', req['HTTP_VERSION']
+    assert_equal '/hello-world', req['REQUEST_URI']
+    assert_equal 'GET', req['REQUEST_METHOD']
+    assert_nil req['FRAGMENT']
+    assert_nil req['QUERY_STRING']
   end
-
+
   def test_parse_strange_headers
     parser = HttpParser.new
     req = {}
     should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
-    nread = parser.execute(req, should_be_good, 0)
-    assert_equal should_be_good.length, nread
-    assert parser.finished?
-    assert !parser.error?
+    assert parser.execute(req, should_be_good)
 
     # ref: http://thread.gmane.org/gmane.comp.lang.ruby.Unicorn.devel/37/focus=45
     # (note we got 'pen' mixed up with 'pound' in that thread,
@@ -49,10 +56,7 @@ class HttpParserTest < Test::Unit::TestCase
     # nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
     # parser = HttpParser.new
     # req = {}
-    # nread = parser.execute(req, nasty_pound_header, 0)
-    # assert_equal nasty_pound_header.length, nread
-    # assert parser.finished?
-    # assert !parser.error?
+    # assert parser.execute(req, nasty_pound_header, 0)
   end
 
   def test_parse_ie6_urls
@@ -66,10 +70,7 @@ class HttpParserTest < Test::Unit::TestCase
       parser = HttpParser.new
       req = {}
       sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
-      nread = parser.execute(req, sorta_safe, 0)
-      assert_equal sorta_safe.length, nread
-      assert parser.finished?
-      assert !parser.error?
+      assert parser.execute(req, sorta_safe)
     end
   end
   
@@ -78,28 +79,68 @@ class HttpParserTest < Test::Unit::TestCase
     req = {}
     bad_http = "GET / SsUTF/1.1"
 
-    error = false
-    begin
-      nread = parser.execute(req, bad_http, 0)
-    rescue => details
-      error = true
-    end
+    assert_raises(HttpParserError) { parser.execute(req, bad_http) }
+    parser.reset
+    assert(parser.execute({}, "GET / HTTP/1.0\r\n\r\n"))
+  end
 
-    assert error, "failed to throw exception"
-    assert !parser.finished?, "Parser shouldn't be finished"
-    assert parser.error?, "Parser SHOULD have error"
+  def test_piecemeal
+    parser = HttpParser.new
+    req = {}
+    http = "GET"
+    assert ! parser.execute(req, http)
+    assert_raises(HttpParserError) { parser.execute(req, http) }
+    assert ! parser.execute(req, http << " / HTTP/1.0")
+    assert_equal '/', req['REQUEST_PATH']
+    assert_equal '/', req['REQUEST_URI']
+    assert_equal 'GET', req['REQUEST_METHOD']
+    assert ! parser.execute(req, http << "\r\n")
+    assert_equal 'HTTP/1.0', req['HTTP_VERSION']
+    assert ! parser.execute(req, http << "\r")
+    assert parser.execute(req, http << "\n")
+    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_nil req['FRAGMENT']
+    assert_nil req['QUERY_STRING']
+  end
+
+  def test_put_body_oneshot
+    parser = HttpParser.new
+    req = {}
+    http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
+    assert parser.execute(req, http)
+    assert_equal '/', req['REQUEST_PATH']
+    assert_equal '/', req['REQUEST_URI']
+    assert_equal 'PUT', req['REQUEST_METHOD']
+    assert_equal 'HTTP/1.0', req['HTTP_VERSION']
+    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_equal "abcde", req['HTTP_BODY']
+  end
+
+  def test_put_body_later
+    parser = HttpParser.new
+    req = {}
+    http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
+    assert parser.execute(req, http)
+    assert_equal '/l', req['REQUEST_PATH']
+    assert_equal '/l', req['REQUEST_URI']
+    assert_equal 'PUT', req['REQUEST_METHOD']
+    assert_equal 'HTTP/1.0', req['HTTP_VERSION']
+    assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
+    assert_equal "", req['HTTP_BODY']
   end
 
   def test_fragment_in_uri
     parser = HttpParser.new
     req = {}
     get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
+    ok = false
     assert_nothing_raised do
-      parser.execute(req, get, 0)
+      ok = parser.execute(req, get)
     end
-    assert parser.finished?
+    assert ok
     assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
     assert_equal 'posts-17408', req['FRAGMENT']
+    assert_equal 'page=1', req['QUERY_STRING']
   end
 
   # lame random garbage maker
@@ -124,7 +165,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get, 0)
+        parser.execute({}, get)
         parser.reset
       end
     end
@@ -133,7 +174,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get, 0)
+        parser.execute({}, get)
         parser.reset
       end
     end
@@ -142,7 +183,7 @@ class HttpParserTest < Test::Unit::TestCase
     get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
     get << "X-Test: test\r\n" * (80 * 1024)
     assert_raises Unicorn::HttpParserError do
-      parser.execute({}, get, 0)
+      parser.execute({}, get)
       parser.reset
     end
 
@@ -150,7 +191,7 @@ class HttpParserTest < Test::Unit::TestCase
     10.times do |c|
       get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
       assert_raises Unicorn::HttpParserError do
-        parser.execute({}, get, 0)
+        parser.execute({}, get)
         parser.reset
       end
     end
diff --git a/test/unit/test_socket_helper.rb b/test/unit/test_socket_helper.rb
new file mode 100644
index 0000000..23fa44c
--- /dev/null
+++ b/test/unit/test_socket_helper.rb
@@ -0,0 +1,159 @@
+require 'test/test_helper'
+require 'tempfile'
+
+class TestSocketHelper < Test::Unit::TestCase
+  include Unicorn::SocketHelper
+  attr_reader :logger
+  GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
+
+  def setup
+    @log_tmp = Tempfile.new 'logger'
+    @logger = Logger.new(@log_tmp.path)
+    @test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
+  end
+
+  def test_bind_listen_tcp
+    port = unused_port @test_addr
+    @tcp_listener_name = "#@test_addr:#{port}"
+    @tcp_listener = bind_listen(@tcp_listener_name)
+    assert Socket === @tcp_listener
+    assert_equal @tcp_listener_name, sock_name(@tcp_listener)
+  end
+
+  def test_bind_listen_options
+    port = unused_port @test_addr
+    tcp_listener_name = "#@test_addr:#{port}"
+    tmp = Tempfile.new 'unix.sock'
+    unix_listener_name = tmp.path
+    File.unlink(tmp.path)
+    [ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
+      { :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
+    ].each do |opts|
+      assert_nothing_raised do
+        tcp_listener = bind_listen(tcp_listener_name, opts)
+        assert Socket === tcp_listener
+        tcp_listener.close
+        unix_listener = bind_listen(unix_listener_name, opts)
+        assert Socket === unix_listener
+        unix_listener.close
+      end
+    end
+    #system('cat', @log_tmp.path)
+  end
+
+  def test_bind_listen_unix
+    tmp = Tempfile.new 'unix.sock'
+    @unix_listener_path = tmp.path
+    File.unlink(@unix_listener_path)
+    @unix_listener = bind_listen(@unix_listener_path)
+    assert Socket === @unix_listener
+    assert_equal @unix_listener_path, sock_name(@unix_listener)
+  end
+
+  def test_bind_listen_unix_idempotent
+    test_bind_listen_unix
+    a = bind_listen(@unix_listener)
+    assert_equal a.fileno, @unix_listener.fileno
+    unix_server = server_cast(@unix_listener)
+    a = bind_listen(unix_server)
+    assert_equal a.fileno, unix_server.fileno
+    assert_equal a.fileno, @unix_listener.fileno
+  end
+
+  def test_bind_listen_tcp_idempotent
+    test_bind_listen_tcp
+    a = bind_listen(@tcp_listener)
+    assert_equal a.fileno, @tcp_listener.fileno
+    tcp_server = server_cast(@tcp_listener)
+    a = bind_listen(tcp_server)
+    assert_equal a.fileno, tcp_server.fileno
+    assert_equal a.fileno, @tcp_listener.fileno
+  end
+
+  def test_bind_listen_unix_rebind
+    test_bind_listen_unix
+    new_listener = bind_listen(@unix_listener_path)
+    assert Socket === new_listener
+    assert new_listener.fileno != @unix_listener.fileno
+    assert_equal sock_name(new_listener), sock_name(@unix_listener)
+    assert_equal @unix_listener_path, sock_name(new_listener)
+    pid = fork do
+      client = server_cast(new_listener).accept
+      client.syswrite('abcde')
+      exit 0
+    end
+    s = UNIXSocket.new(@unix_listener_path)
+    IO.select([s])
+    assert_equal 'abcde', s.sysread(5)
+    pid, status = Process.waitpid2(pid)
+    assert status.success?
+  end
+
+  def test_server_cast
+    assert_nothing_raised do
+      test_bind_listen_unix
+      test_bind_listen_tcp
+    end
+    @unix_server = server_cast(@unix_listener)
+    assert_equal @unix_listener.fileno, @unix_server.fileno
+    assert UNIXServer === @unix_server
+    assert File.socket?(@unix_server.path)
+    assert_equal @unix_listener_path, sock_name(@unix_server)
+
+    @tcp_server = server_cast(@tcp_listener)
+    assert_equal @tcp_listener.fileno, @tcp_server.fileno
+    assert TCPServer === @tcp_server
+    assert_equal @tcp_listener_name, sock_name(@tcp_server)
+  end
+
+  def test_sock_name
+    test_server_cast
+    sock_name(@unix_server)
+  end
+
+  def test_tcp_unicorn_peeraddr
+    test_bind_listen_tcp
+    @tcp_server = server_cast(@tcp_listener)
+    tmp = Tempfile.new 'shared'
+    pid = fork do
+      client = @tcp_server.accept
+      IO.select([client])
+      assert_equal GET_SLASH, client.sysread(GET_SLASH.size)
+      tmp.syswrite "#{client.unicorn_peeraddr}"
+      exit 0
+    end
+    host, port = sock_name(@tcp_server).split(/:/)
+    client = TCPSocket.new(host, port.to_i)
+    client.syswrite(GET_SLASH)
+
+    pid, status = Process.waitpid2(pid)
+    assert_nothing_raised { client.close }
+    assert status.success?
+    tmp.sysseek 0
+    assert_equal @test_addr, tmp.sysread(4096)
+    tmp.sysseek 0
+  end
+
+  def test_unix_unicorn_peeraddr
+    test_bind_listen_unix
+    @unix_server = server_cast(@unix_listener)
+    tmp = Tempfile.new 'shared'
+    pid = fork do
+      client = @unix_server.accept
+      IO.select([client])
+      assert_equal GET_SLASH, client.sysread(4096)
+      tmp.syswrite "#{client.unicorn_peeraddr}"
+      exit 0
+    end
+    client = UNIXSocket.new(@unix_listener_path)
+    client.syswrite(GET_SLASH)
+
+    pid, status = Process.waitpid2(pid)
+    assert_nothing_raised { client.close }
+    assert status.success?
+    tmp.sysseek 0
+    assert_equal '127.0.0.1', tmp.sysread(4096)
+    tmp.sysseek 0
+  end
+
+end