about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-04-16 15:04:28 -0700
committerEric Wong <normalperson@yhbt.net>2009-04-16 15:06:41 -0700
commit66841a0164bc03eddb7a6ac31e3923302dbc5146 (patch)
treeaa25c020b1d59bfe29aeb3b83baec51c968fd4d0
parentc132287b0e0485412224e681244b453f5577479a (diff)
downloadunicorn-66841a0164bc03eddb7a6ac31e3923302dbc5146.tar.gz
There are weird (and possibly broken) clients out there that
require it despite being present in the first line of the
response.  So be nice and accomodate them.  Keep in mind that
the Rack SPEC explicitly forbids this header from being in the
headers returned by the Rack-based application; so we have to
always inject it ourselves and ignore it if the application
sets it.
-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)