From 25bbb88bcc16c1c0a10efc99472b05e9f6b45861 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 6 Feb 2009 14:52:10 -0800 Subject: Get rid of HeaderOut and simplify HttpResponse Just stuff what little logic we had for it into HttpResponse since Rack takes care of the rest for us. Put the HTTP_STATUS_HEADERS hash in HttpResponse since we're the only user of it. Also, change HttpResponse.send to HttpResponse.write to avoid overriding the default method. --- lib/unicorn.rb | 25 ++++++++++++----------- lib/unicorn/const.rb | 6 ------ lib/unicorn/header_out.rb | 47 -------------------------------------------- lib/unicorn/http_response.rb | 44 ++++++++++++++++++++++++++++------------- test/unit/test_response.rb | 6 +++--- 5 files changed, 47 insertions(+), 81 deletions(-) delete mode 100644 lib/unicorn/header_out.rb diff --git a/lib/unicorn.rb b/lib/unicorn.rb index a301df6..af99474 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -18,7 +18,6 @@ require 'rack' require 'unicorn/socket' require 'unicorn/const' require 'unicorn/http_request' -require 'unicorn/header_out' require 'unicorn/http_response' # Unicorn module containing all of the classes (include C extensions) for running @@ -66,9 +65,11 @@ module Unicorn # HttpServer.workers.join to join the thread that's processing # incoming requests on the socket. def initialize(app, options = {}) - @app = app + @app = app @workers = WorkerTable.new - + @parser = HttpParser.new + @params = Hash.new + (DEFAULTS.to_a + options.to_a).each do |key, value| instance_variable_set("@#{key.to_s.downcase}", value) end @@ -83,9 +84,9 @@ module Unicorn # thinks they can make it faster is more than welcome to take a crack at it. def process_client(client) begin - parser = HttpParser.new - params = Hash.new - request = nil + parser, params = @parser, @params + parser.reset + params.clear data = client.readpartial(Const::CHUNK_SIZE) nparsed = 0 @@ -117,10 +118,9 @@ module Unicorn params[Const::REMOTE_ADDR] = client.unicorn_peeraddr.last # Select handlers that want more detailed request notification - request = $http_request ||= HttpRequest.new(logger) - env = request.consume(params, client) or break + env = @request.consume(params, client) or break app_response = @app.call(env) - HttpResponse.send(client, app_response) + HttpResponse.write(client, app_response) break #done else # Parser is not done, queue up more data to read and continue parsing @@ -152,7 +152,7 @@ module Unicorn logger.error "Client error: #{e.inspect}" logger.error e.backtrace.join("\n") end - request.reset! if request + @request.reset! end end @@ -167,7 +167,10 @@ module Unicorn (1..@nr_workers).each do |worker_nr| pid = fork do - nr, alive, listeners = 0, true, @listeners + nr = 0 + alive = true + listeners = @listeners + @request = HttpRequest.new(logger) trap('TERM') { exit 0 } trap('QUIT') do alive = false diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb index 7c236c6..73c334c 100644 --- a/lib/unicorn/const.rb +++ b/lib/unicorn/const.rb @@ -44,12 +44,6 @@ module Unicorn 505 => 'HTTP Version not supported' } - HTTP_STATUS_HEADERS = HTTP_STATUS_CODES.inject({}) do |hash, (code, text)| - text.freeze - hash[code] = "HTTP/1.1 #{code} #{text}\r\nConnection: close\r\n".freeze - hash - end - # Frequently used constants when constructing requests or responses. Many times # 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. diff --git a/lib/unicorn/header_out.rb b/lib/unicorn/header_out.rb deleted file mode 100644 index 95f2633..0000000 --- a/lib/unicorn/header_out.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Unicorn - # This class implements a simple way of constructing the HTTP headers dynamically - # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for - # information on how this is used. - # - # One consequence of this write-only nature is that you can write multiple headers - # by just doing them twice (which is sometimes needed in HTTP), but that the normal - # semantics for Hash (where doing an insert replaces) is not there. - class HeaderOut - ALLOWED_DUPLICATES = { - 'Set-Cookie' => true, - 'Set-Cookie2' => true, - 'Warning' => true, - 'WWW-Authenticate' => true, - }.freeze - - def initialize - @sent = { Const::CONNECTION => true } - @out = [] - end - - def reset! - @sent.clear - @out.clear - @sent[Const::CONNECTION] = true - end - - def merge!(hash) - hash.each do |key, value| - self[key] = value - end - end - - # Simply writes "#{key}: #{value}" to an output buffer. - def[]=(key,value) - if not @sent.has_key?(key) or ALLOWED_DUPLICATES.has_key?(key) - @sent[key] = true - @out << "#{key}: #{value}\r\n" - end - end - - def to_s - @out.join - end - - end -end diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb index 4ffe64b..e2a4e2f 100644 --- a/lib/unicorn/http_response.rb +++ b/lib/unicorn/http_response.rb @@ -1,14 +1,13 @@ module Unicorn - # Writes a Rack response to your client using the HTTP/1.1 specification. # You use it by simply doing: # # status, headers, body = rack_app.call(env) - # HttpResponse.send(socket, [ status, headers, body ]) + # HttpResponse.write(socket, [ status, headers, body ]) # - # Most header correctness (including Content-Length) is the job of - # Rack, with the exception of the "Connection: close" and "Date" - # headers. + # Most header correctness (including Content-Length and Content-Type) + # is the job of Rack, with the exception of the "Connection: close" + # 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 @@ -20,19 +19,36 @@ module Unicorn class HttpResponse - # we'll have one of these per-process - HEADERS = HeaderOut.new unless defined?(HEADERS) + # enforce "Connection: close" usage on all our responses + HTTP_STATUS_HEADERS = HTTP_STATUS_CODES.inject({}) do |hash, (code, text)| + hash[code] = "HTTP/1.1 #{code} #{text}\r\nConnection: close".freeze + hash + end.freeze + + # headers we allow duplicates for + ALLOWED_DUPLICATES = { + 'Set-Cookie' => true, + 'Set-Cookie2' => true, + 'Warning' => true, + 'WWW-Authenticate' => true, + }.freeze - def self.send(socket, rack_response) + def self.write(socket, rack_response) status, headers, body = rack_response - HEADERS.reset! - # Rack does not set Date, but don't worry about Content-Length, - # since Rack enforces that in Rack::Lint - HEADERS[Const::DATE] = Time.now.httpdate - HEADERS.merge!(headers) + # Rack does not set/require Date, but don't worry about Content-Length + # since Rack enforces that in Rack::Lint. + out = [ "#{Const::DATE}: #{Time.now.httpdate}\r\n" ] + sent = { Const::CONNECTION => true, Const::DATE => true } + + headers.each_pair do |key, value| + if ! sent[key] || ALLOWED_DUPLICATES[key] + sent[key] = true + out << "#{key}: #{value}\r\n" + end + end - socket.write("#{HTTP_STATUS_HEADERS[status]}#{HEADERS.to_s}\r\n") + socket.write("#{HTTP_STATUS_HEADERS[status]}\r\n#{out.join}\r\n") body.each { |chunk| socket.write(chunk) } end diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb index b142c07..5d60594 100644 --- a/test/unit/test_response.rb +++ b/test/unit/test_response.rb @@ -12,21 +12,21 @@ class ResponseTest < Test::Unit::TestCase def test_response_headers out = StringIO.new - HttpResponse.send(out,[200, {"X-Whatever" => "stuff"}, ["cool"]]) + HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]]) assert out.length > 0, "output didn't have data" end def test_response_200 io = StringIO.new - HttpResponse.send(io, [200, {}, []]) + HttpResponse.write(io, [200, {}, []]) assert io.length > 0, "output didn't have data" end def test_response_with_default_reason code = 400 io = StringIO.new - HttpResponse.send(io, [code, {}, []]) + HttpResponse.write(io, [code, {}, []]) io.rewind assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase") end -- cgit v1.2.3-24-ge0c7