summary refs log tree commit
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2015-09-21 20:36:16 +0000
committerEric Wong <e@80x24.org>2015-09-21 21:11:50 +0000
commitf769efe714f5b8b417e0440ce05f8b4e4b504c57 (patch)
tree3a65d23f8e3eb6fc0b3261c0c380f650f5ebaa17
parentc28f271d0c91f45e13bfa8f07bed445ef91f41de (diff)
downloadrack-zlib-disco.tar.gz
deflater: always finish zlib stream before closing zlib-disco
This helps avoid Zlib::DataError when a client disconnects on
the server while the server is writing the response.

This fixes the following backtraces on my server:

  data error (Zlib::DataError)
  rack/deflater.rb:124:in `close'
  rack/deflater.rb:124:in `ensure in each'
  rack/deflater.rb:124:in `each'
  rack/chunked.rb:23:in `each'
  ...
-rw-r--r--lib/rack/deflater.rb3
-rw-r--r--test/spec_deflater.rb25
2 files changed, 27 insertions, 1 deletions
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb
index 19dc7551..62a11243 100644
--- a/lib/rack/deflater.rb
+++ b/lib/rack/deflater.rb
@@ -118,8 +118,9 @@ module Rack
       def each
         deflator = ::Zlib::Deflate.new(*DEFLATE_ARGS)
         @body.each { |part| yield deflator.deflate(part, Zlib::SYNC_FLUSH) }
-        yield deflator.finish
+        yield fin = deflator.finish
       ensure
+        deflator.finish unless fin
         deflator.close
       end
 
diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb
index 2c7a428c..ba7ec5d3 100644
--- a/test/spec_deflater.rb
+++ b/test/spec_deflater.rb
@@ -114,6 +114,31 @@ describe Rack::Deflater do
     end
   end
 
+  it 'does not raise when a client aborts reading' do
+    app_body = Object.new
+    class << app_body; def each; yield('foo'); yield('bar'); end; end
+    opts = { 'skip_body_verify' => true }
+    verify(200, app_body, 'deflate', opts) do |status, headers, body|
+      headers.must_equal({
+        'Content-Encoding' => 'deflate',
+        'Vary' => 'Accept-Encoding',
+        'Content-Type' => 'text/plain'
+      })
+
+      buf = []
+      inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
+      FakeDisconnect = Class.new(RuntimeError)
+      assert_raises(FakeDisconnect, "not Zlib::DataError not raised") do
+        body.each do |part|
+          buf << inflater.inflate(part)
+          raise FakeDisconnect
+        end
+      end
+      assert_raises(Zlib::BufError) { inflater.finish }
+      buf.must_equal(%w(foo))
+    end
+  end
+
   # TODO: This is really just a special case of the above...
   it 'be able to deflate String bodies' do
     verify(200, 'Hello world!', 'deflate') do |status, headers, body|