about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-01-29 21:00:32 +0000
committerEric Wong <normalperson@yhbt.net>2013-01-29 21:00:32 +0000
commitb73299a053b305098d5d68634fa928ec71aa4eac (patch)
tree707db7cf24469ad9cea91c3ef68c95e5b48b7bfb /lib
parentc43113e350aabb78c30ba64884328458db85c901 (diff)
parentfedb5e50829e6dfad30ca18ea525c812eccbec70 (diff)
downloadunicorn-b73299a053b305098d5d68634fa928ec71aa4eac.tar.gz
* hijack:
  ignore normal Rack response at request-time hijack
  support for Rack hijack in request and response
Diffstat (limited to 'lib')
-rw-r--r--lib/unicorn/http_request.rb29
-rw-r--r--lib/unicorn/http_response.rb40
-rw-r--r--lib/unicorn/http_server.rb8
3 files changed, 67 insertions, 10 deletions
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index 79ead2e..3795b3b 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -91,6 +91,35 @@ class Unicorn::HttpParser
 
     e[RACK_INPUT] = 0 == content_length ?
                     NULL_IO : @@input_class.new(socket, self)
+    hijack_setup(e, socket)
     e.merge!(DEFAULTS)
   end
+
+  # Rack 1.5.0 (protocol version 1.2) adds hijack request support
+  if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
+    DEFAULTS["rack.hijack?"] = true
+
+    # FIXME: asking for clarification about this in
+    # http://mid.gmane.org/20130122100802.GA28585@dcvr.yhbt.net
+    DEFAULTS["rack.version"] = [1, 2]
+
+    RACK_HIJACK = "rack.hijack".freeze
+    RACK_HIJACK_IO = "rack.hijack_io".freeze
+
+    def hijacked?
+      env.include?(RACK_HIJACK_IO)
+    end
+
+    def hijack_setup(e, socket)
+      e[RACK_HIJACK] = proc { e[RACK_HIJACK_IO] ||= socket }
+    end
+  else
+    # old Rack, do nothing.
+    def hijack_setup(e, _)
+    end
+
+    def hijacked?
+      false
+    end
+  end
 end
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 579d957..083951c 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -25,6 +25,7 @@ module Unicorn::HttpResponse
   def http_response_write(socket, status, headers, body,
                           response_start_sent=false)
     status = CODES[status.to_i] || status
+    hijack = nil
 
     http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
     if headers
@@ -33,19 +34,42 @@ module Unicorn::HttpResponse
             "Status: #{status}\r\n" \
             "Connection: close\r\n"
       headers.each do |key, value|
-        next if %r{\A(?:Date\z|Connection\z)}i =~ key
-        if value =~ /\n/
-          # avoiding blank, key-only cookies with /\n+/
-          buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
+        case key
+        when %r{\A(?:Date\z|Connection\z)}i
+          next
+        when "rack.hijack"
+          # this was an illegal key in Rack < 1.5, so it should be
+          # OK to silently discard it for those older versions
+          hijack = hijack_prepare(value)
         else
-          buf << "#{key}: #{value}\r\n"
+          if value =~ /\n/
+            # avoiding blank, key-only cookies with /\n+/
+            buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
+          else
+            buf << "#{key}: #{value}\r\n"
+          end
         end
       end
       socket.write(buf << CRLF)
     end
 
-    body.each { |chunk| socket.write(chunk) }
-    ensure
-      body.respond_to?(:close) and body.close
+    if hijack
+      body = nil # ensure we do not close body
+      hijack.call(socket)
+    else
+      body.each { |chunk| socket.write(chunk) }
+    end
+  ensure
+    body.respond_to?(:close) and body.close
+  end
+
+  # Rack 1.5.0 (protocol version 1.2) adds response hijacking support
+  if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
+    def hijack_prepare(value)
+      value
+    end
+  else
+    def hijack_prepare(_)
+    end
   end
 end
diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb
index aa98aeb..cc0a705 100644
--- a/lib/unicorn/http_server.rb
+++ b/lib/unicorn/http_server.rb
@@ -550,17 +550,21 @@ class Unicorn::HttpServer
   # in 3 easy steps: read request, call app, write app response
   def process_client(client)
     status, headers, body = @app.call(env = @request.read(client))
+    return if @request.hijacked?
 
     if 100 == status.to_i
       client.write(expect_100_response)
       env.delete(Unicorn::Const::HTTP_EXPECT)
       status, headers, body = @app.call(env)
+      return if @request.hijacked?
     end
     @request.headers? or headers = nil
     http_response_write(client, status, headers, body,
                         @request.response_start_sent)
-    client.shutdown # in case of fork() in Rack app
-    client.close # flush and uncork socket immediately, no keepalive
+    unless client.closed? # rack.hijack may've close this for us
+      client.shutdown # in case of fork() in Rack app
+      client.close # flush and uncork socket immediately, no keepalive
+    end
   rescue => e
     handle_error(client, e)
   end