about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--lib/unicorn/http_response.rb23
-rw-r--r--lib/unicorn/rainbows.rb34
2 files changed, 27 insertions, 30 deletions
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 114243c..3d7cd50 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -5,20 +5,11 @@ module Unicorn
   # You use it by simply doing:
   #
   #   status, headers, body = rack_app.call(env)
-  #   HttpResponse.write(socket, [ status, headers, body ])
+  #   HttpResponse.write(socket, [ status, headers, body ], keepalive)
   #
   # Most header correctness (including Content-Length and Content-Type)
-  # is the job of Rack, with the exception of the "Connection: close"
+  # is the job of Rack, with the exception of the "Connection"
   # and "Date" headers.
-  #
-  # A design decision was made to force the client to not pipeline or
-  # keepalive requests.  HTTP/1.1 pipelining really kills the
-  # performance due to how it has to be handled and how unclear the
-  # standard is.  To fix this the HttpResponse always gives a
-  # "Connection: close" header which forces the client to close right
-  # away.  The bonus for this is that it gives a pretty nice speed boost
-  # to most clients since they can close their connection immediately.
-
   class HttpResponse
 
     # Every standard HTTP code mapped to the appropriate message.
@@ -27,16 +18,19 @@ module Unicorn
       hash
     }
 
+    CONN_CLOSE = "Connection: close\r\n"
+    CONN_ALIVE = "Connection: keep-alive\r\n"
+
     # Rack does not set/require a Date: header.  We always override the
     # Connection: and Date: headers no matter what (if anything) our
     # Rack application sent us.
     SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
 
     # writes the rack_response to socket as an HTTP response
-    def self.write(socket, rack_response)
+    def self.write(socket, rack_response, keepalive = false)
       status, headers, body = rack_response
       status = CODES[status.to_i] || status
-      tmp = []
+      tmp = [ keepalive ? CONN_ALIVE : CONN_CLOSE ]
 
       # Don't bother enforcing duplicate supression, it's a Hash most of
       # the time anyways so just hope our app knows what it's doing
@@ -56,10 +50,9 @@ module Unicorn
       socket.write("HTTP/1.1 #{status}\r\n" \
                    "Date: #{Time.now.httpdate}\r\n" \
                    "Status: #{status}\r\n" \
-                   "Connection: close\r\n" \
                    "#{tmp.join(Z)}\r\n")
       body.each { |chunk| socket.write(chunk) }
-      socket.close # flushes and uncorks the socket immediately
+      keepalive or socket.close # flushes and uncorks the socket immediately
       ensure
         body.respond_to?(:close) and body.close rescue nil
     end
diff --git a/lib/unicorn/rainbows.rb b/lib/unicorn/rainbows.rb
index 21ec43b..f5f6a17 100644
--- a/lib/unicorn/rainbows.rb
+++ b/lib/unicorn/rainbows.rb
@@ -12,29 +12,33 @@ module Unicorn
       "rack.multithread" => true,
       "SERVER_SOFTWARE" => "Unicorn Rainbows! #{Const::UNICORN_VERSION}",
     })
-    SKIP = HttpResponse::SKIP
-    CODES = HttpResponse::CODES
 
     # once a client is accepted, it is processed in its entirety here
     # in 3 easy steps: read request, call app, write app response
     def process_client(client)
-      env = { Const::REMOTE_ADDR => client.remote_addr }
+      env = {}
+      remote_addr = client.remote_addr
       hp = HttpParser.new
       buf = client.read
-      while ! hp.headers(env, buf)
-        buf << client.read
-      end
 
-      env[Const::RACK_INPUT] = 0 == hp.content_length ?
-               HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
-      response = app.call(env.update(DEFAULTS))
+      begin
+        while ! hp.headers(env, buf)
+          buf << client.read
+        end
+
+        env[Const::REMOTE_ADDR] = remote_addr
+        env[Const::RACK_INPUT] = 0 == hp.content_length ?
+                 HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
+        response = app.call(env.update(DEFAULTS))
 
-      if 100 == response.first.to_i
-        client.write(Const::EXPECT_100_RESPONSE)
-        env.delete(Const::HTTP_EXPECT)
-        response = app.call(env)
-      end
-      HttpResponse.write(client, response)
+        if 100 == response.first.to_i
+          client.write(Const::EXPECT_100_RESPONSE)
+          env.delete(Const::HTTP_EXPECT)
+          response = app.call(env)
+        end
+        HttpResponse.write(client, response, hp.keepalive?)
+      end while hp.keepalive? and hp.reset.nil? and env.clear
+      client.close
     # if we get any error, try to write something back to the client
     # assuming we haven't closed the socket, but don't get hung up
     # if the socket is already closed or broken.  We'll always ensure