diff options
-rw-r--r-- | lib/unicorn/http_response.rb | 23 | ||||
-rw-r--r-- | lib/unicorn/rainbows.rb | 34 |
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 |