about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--lib/unicorn/http_response.rb10
-rw-r--r--test/unit/test_response.rb21
2 files changed, 28 insertions, 3 deletions
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 98759f1..c0b1081 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -24,12 +24,16 @@ module Unicorn
     # 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 }.freeze
+    SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
 
     # writes the rack_response to socket as an HTTP response
     def self.write(socket, rack_response)
       status, headers, body = rack_response
-      out = [ "Date: #{Time.now.httpdate}" ]
+      status = "#{status} #{HTTP_STATUS_CODES[status]}"
+
+      # Date is required by HTTP/1.1 as long as our clock can be trusted.
+      # Some broken clients require a "Status" header so we accomodate them
+      out = [ "Date: #{Time.now.httpdate}", "Status: #{status}" ]
 
       # 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
@@ -45,7 +49,7 @@ module Unicorn
       # Rack should enforce Content-Length or chunked transfer encoding,
       # so don't worry or care about them.
       socket_write(socket,
-                   "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status]}\r\n" \
+                   "HTTP/1.1 #{status}\r\n" \
                    "Connection: close\r\n" \
                    "#{out.join("\r\n")}\r\n\r\n")
       body.each { |chunk| socket_write(socket, chunk) }
diff --git a/test/unit/test_response.rb b/test/unit/test_response.rb
index 203ae4d..8abc86c 100644
--- a/test/unit/test_response.rb
+++ b/test/unit/test_response.rb
@@ -54,6 +54,27 @@ class ResponseTest < Test::Unit::TestCase
     assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
   end
 
+  # Even though Rack explicitly forbids "Status" in the header hash,
+  # some broken clients still rely on it
+  def test_status_header_added
+    out = StringIO.new
+    HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []])
+    assert out.closed?
+    assert_match(/^Status: 200 OK\r\nX-Whatever: stuff\r\n/, out.string)
+  end
+
+  # we always favor the code returned by the application, since "Status"
+  # in the header hash is not allowed by Rack (but not every app is
+  # fully Rack-compliant).
+  def test_status_header_ignores_app_hash
+    out = StringIO.new
+    header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
+    HttpResponse.write(out,[200, header_hash, []])
+    assert out.closed?
+    assert_match(/^Status: 200 OK\r\nX-Whatever: stuff\r\n/, out.string)
+    assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
+  end
+
   def test_body_closed
     expect_body = %w(1 2 3 4).join("\n")
     body = StringIO.new(expect_body)