about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-08-19 02:03:58 -0700
committerEric Wong <normalperson@yhbt.net>2009-08-19 02:03:58 -0700
commit14d429e43e2aedb2615dcc3b7b013633f9a3b760 (patch)
treeee8b004bef228ba5b39c3af46e69b051cafa1ede /lib
parentef3ce3c839a61acbb6ac0c138e4a90d449163cf8 (diff)
downloadunicorn-14d429e43e2aedb2615dcc3b7b013633f9a3b760.tar.gz
keepalive/pipelining absolutely does not make sense with classic
Unicorn, but it does make sense when connections are cheap with
Revactor.  Of course any server that serves multiple clients
concurrently within the same process is less robust anyways.
Diffstat (limited to 'lib')
-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