summary refs log tree commit
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-12-24 22:34:27 +0000
committerEric Wong <normalperson@yhbt.net>2009-12-24 22:34:27 +0000
commit28f19d7d1f1255fc920a42640e2d7f24b37f7eaf (patch)
treeb03e115d99aa27b59bd0b555110e39fd5047d5ae
parentaea2d608fe592a665741087a8d6d31fcf26848ec (diff)
downloadrack-28f19d7d1f1255fc920a42640e2d7f24b37f7eaf.tar.gz
Rack::Lint understands "rack.io" env
-rw-r--r--lib/rack/lint.rb45
-rw-r--r--test/spec_rack_lint.rb40
2 files changed, 85 insertions, 0 deletions
diff --git a/lib/rack/lint.rb b/lib/rack/lint.rb
index 493982ac..9c10c7d5 100644
--- a/lib/rack/lint.rb
+++ b/lib/rack/lint.rb
@@ -224,6 +224,8 @@ module Rack
       check_input env["rack.input"]
       ## * There must be a valid error stream in <tt>rack.errors</tt>.
       check_error env["rack.errors"]
+      ## * There may be a bidirectional IO in <tt>rack.io</tt>.
+      check_io env["rack.io"]
 
       ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
       assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
@@ -281,6 +283,49 @@ module Rack
       }
     end
 
+    ## === The IO Stream
+    ##
+    ## An optional "raw" IO object which is both read and writable.
+    ## This may be used to implement alternative/tunneled protocols
+    ## such as Web Sockets or used to detect client disconnects
+    ## before performing long/expensive operations.
+    def check_io(io)
+      return if io.nil?
+
+      ## When applicable, its external encoding must be "ASCII-8BIT" and it
+      ## must be opened in binary mode, for Ruby 1.9 compatibility.
+      assert("rack.io #{io} does not have ASCII-8BIT as its external encoding") {
+        io.external_encoding.name == "ASCII-8BIT"
+      } if io.respond_to?(:external_encoding)
+      assert("rack.io is not opened in binary mode") {
+        io.binmode?
+      } if io.respond_to?(:binmode?)
+
+      ## Allow StringIO for testing
+      return if io.kind_of? StringIO
+
+      ## IO.select() is useful for detecting if a client is still
+      ## alive before performing long or expensive operations.
+      ## IO.select() also validates that the IO object is usable
+      ## by other IO frameworks such as Rev or EventMachine.
+      assert("rack.io is not a readable IO") {
+        begin
+          IO.select([io], nil, nil, 0.0)
+          true
+        rescue
+          false
+        end
+      }
+      assert("rack.io is not a writable IO") {
+        begin
+          IO.select(nil, [io], nil, 0.0)
+          true
+        rescue
+          false
+        end
+      }
+    end
+
     class InputWrapper
       include Assertion
 
diff --git a/test/spec_rack_lint.rb b/test/spec_rack_lint.rb
index bbf75c17..3daa5cac 100644
--- a/test/spec_rack_lint.rb
+++ b/test/spec_rack_lint.rb
@@ -17,6 +17,17 @@ context "Rack::Lint" do
     }.should.not.raise
   end
 
+  specify "passes valid request with rack.io" do
+    lambda {
+      io = StringIO.new
+      io.binmode if io.respond_to?(:binmode)
+      io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
+      Rack::Lint.new(lambda { |env|
+                       [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
+                     }).call(env("rack.io" => io))
+    }.should.not.raise
+  end
+
   specify "notices fatal errors" do
     lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
       message.should.match(/No env given/)
@@ -139,6 +150,35 @@ context "Rack::Lint" do
       message.should.match(/does not have ASCII-8BIT as its external encoding/)
   end
 
+  specify "notices io errors" do
+    lambda {
+      Rack::Lint.new(nil).call(env("rack.io" => true))
+    }.should.raise(Rack::Lint::LintError).
+      message.should.match(/is not a readable IO/)
+
+    lambda {
+      io = Object.new
+      def io.binmode?
+        false
+      end
+      Rack::Lint.new(nil).call(env("rack.io" => io))
+    }.should.raise(Rack::Lint::LintError).
+      message.should.match(/is not opened in binary mode/)
+
+    lambda {
+      io = Object.new
+      def io.external_encoding
+        result = Object.new
+        def result.name
+          "US-ASCII"
+        end
+        result
+      end
+      Rack::Lint.new(nil).call(env("rack.io" => io))
+    }.should.raise(Rack::Lint::LintError).
+      message.should.match(/does not have ASCII-8BIT as its external encoding/)
+  end
+
   specify "notices error errors" do
     lambda {
       Rack::Lint.new(nil).call(env("rack.errors" => ""))