Permalink
Browse files

Add straw man for the after-headers hijack

  • Loading branch information...
1 parent 36a2145 commit 8a311fb0ba3f906208af342066f2cc5fdef038ac @raggi raggi committed Jan 5, 2013
Showing with 73 additions and 0 deletions.
  1. +25 −0 SPEC
  2. +48 −0 lib/rack/lint.rb
View
@@ -134,6 +134,7 @@ The error stream must respond to +puts+, +write+ and +flush+.
in order to make the error appear for sure.
* +close+ must never be called on the error stream.
=== Hijacking
+==== Request (before status)
If rack.hijack? is true then rack.hijack must respond to #call.
rack.hijack should return the io that will also be assigned (or is
already present, in rack.hijack_io.
@@ -162,12 +163,36 @@ provides the minimum of specification and support.
If rack.hijack? is false, then rack.hijack should not be set.
If rack.hijack? is false, then rack.hijack_io should not be set.
+==== Response (after headers)
+It is also possible to hijack a response after the status and headers
+have been sent.
+In order to do this, an application may set the special header
+<tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
+accepting an argument that conforms to the <tt>rack.hijack_io</tt>
+protocol.
+
+After the headers have been sent, and this hijack callback has been
+called, the application is now responsible for the remaining lifecycle
+of the IO. The application is also responsible for maintaining HTTP
+semantics. Of specific note, in almost all cases in the current SPEC,
+applications will have wanted to specify the header Connection:close in
+HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
+returning hijacked sockets to the web server. For that purpose, use the
+body streaming API instead (progressively yielding strings via each).
+
+Servers must ignore the <tt>body</tt> part of the response tuple when
+the <tt>rack.hijack</tt> response API is in use.
+
+The special response header <tt>rack.hijack</tt> must only be set
+if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
== The Response
=== The Status
This is an HTTP status. When parsed as integer (+to_i+), it must be
greater than or equal to 100.
=== The Headers
The header must respond to +each+, and yield values of key and value.
+Special headers starting "rack." are for communicating with the
+server, and must not be sent back to the client.
The header keys must be Strings.
The header must not contain a +Status+ key,
contain keys with <tt>:</tt> or newlines in their name,
View
@@ -51,6 +51,9 @@ def _call(env)
check_status status
## the *headers*,
check_headers headers
+
+ check_hijack_response headers, env
+
## and the *body*.
check_content_type status, headers
check_content_length status, headers
@@ -447,6 +450,8 @@ def initialize(io)
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
# should not be removed. The whitespace creates paragraphs in the RDoc
# output.
+ #
+ ## ==== Request (before status)
def check_hijack(env)
if env['rack.hijack?']
## If rack.hijack? is true then rack.hijack must respond to #call.
@@ -490,6 +495,45 @@ def check_hijack(env)
end
end
+ ## ==== Response (after headers)
+ ## It is also possible to hijack a response after the status and headers
+ ## have been sent.
+ def check_hijack_response(headers, env)
+ ## In order to do this, an application may set the special header
+ ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
+ ## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
+ ## protocol.
+ ##
+ ## After the headers have been sent, and this hijack callback has been
+ ## called, the application is now responsible for the remaining lifecycle
+ ## of the IO. The application is also responsible for maintaining HTTP
+ ## semantics. Of specific note, in almost all cases in the current SPEC,
+ ## applications will have wanted to specify the header Connection:close in
+ ## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
+ ## returning hijacked sockets to the web server. For that purpose, use the
+ ## body streaming API instead (progressively yielding strings via each).
+ ##
+ ## Servers must ignore the <tt>body</tt> part of the response tuple when
+ ## the <tt>rack.hijack</tt> response API is in use.
+
+ if env['rack.hijack?'] && headers['rack.hijack']
+ assert('rack.hijack header must respond to #call') {
+ headers['rack.hijack'].respond_to? :call
+ }
+ original_hijack = headers['rack.hijack']
+ headers['rack.hijack'] = proc do |io|
+ original_hijack.call HijackWrapper.new(io)
+ end
+ else
+ ##
+ ## The special response header <tt>rack.hijack</tt> must only be set
+ ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
+ assert('rack.hijack header must not be present if server does not support hijacking') {
+ headers['rack.hijack'].nil?
+ }
+ end
+ end
+
## == The Response
## === The Status
@@ -506,6 +550,10 @@ def check_headers(header)
header.respond_to? :each
}
header.each { |key, value|
+ ## Special headers starting "rack." are for communicating with the
+ ## server, and must not be sent back to the client.
+ next if key =~ /^rack\..+$/
+
## The header keys must be Strings.
assert("header key must be a string, was #{key.class}") {
key.kind_of? String

0 comments on commit 8a311fb

Please sign in to comment.