diff options
author | Eric Wong <e@80x24.org> | 2017-06-29 01:59:51 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2017-06-29 02:04:41 +0000 |
commit | 181e56e011f4c321895bfd01f20cccb3ec1cafa5 (patch) | |
tree | ee364be9f4f5fea9787fd0e186c537d68f8a0d85 | |
parent | 1b94e07f8600dd65a8ba1970161e97e32690e05c (diff) | |
download | rack-181e56e011f4c321895bfd01f20cccb3ec1cafa5.tar.gz |
deflater: support "sync: false" option
Flushing after after very flush is great for real-time apps. However, flushing is inefficient when apps use Rack::Response to generate many small writes (e.g. Rack::Lobster). Allow users to disable the default "sync: true" behavior to reduce bandwidth usage, otherwise using Rack::Deflater can lead to using more bandwidth than without it.
-rw-r--r-- | lib/rack/deflater.rb | 11 | ||||
-rw-r--r-- | test/spec_deflater.rb | 34 |
2 files changed, 42 insertions, 3 deletions
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb index d575adfe..821f708b 100644 --- a/lib/rack/deflater.rb +++ b/lib/rack/deflater.rb @@ -24,11 +24,15 @@ module Rack # 'if' - a lambda enabling / disabling deflation based on returned boolean value # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.map(&:bytesize).reduce(0, :+) > 512 } # 'include' - a list of content types that should be compressed + # 'sync' - Flushing after every chunk reduces latency for + # time-sensitive streaming applications, but hurts + # compression and throughput. Defaults to `true'. def initialize(app, options = {}) @app = app @condition = options[:if] @compressible_types = options[:include] + @sync = options[:sync] == false ? false : true end def call(env) @@ -56,7 +60,7 @@ module Rack headers.delete('Content-Length') mtime = headers.key?("Last-Modified") ? Time.httpdate(headers["Last-Modified"]) : Time.now - [status, headers, GzipStream.new(body, mtime)] + [status, headers, GzipStream.new(body, mtime, @sync)] when "identity" [status, headers, body] when nil @@ -67,7 +71,8 @@ module Rack end class GzipStream - def initialize(body, mtime) + def initialize(body, mtime, sync) + @sync = sync @body = body @mtime = mtime end @@ -78,7 +83,7 @@ module Rack gzip.mtime = @mtime @body.each { |part| gzip.write(part) - gzip.flush + gzip.flush if @sync } ensure gzip.close diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb index 0f27c859..410a1438 100644 --- a/test/spec_deflater.rb +++ b/test/spec_deflater.rb @@ -372,4 +372,38 @@ describe Rack::Deflater do verify(200, response, 'gzip', options) end + + it 'will honor sync: false to avoid unnecessary flushing' do + app_body = Object.new + class << app_body + def each + (0..20).each { |i| yield "hello\n".freeze } + end + end + + options = { + 'deflater_options' => { :sync => false }, + 'app_body' => app_body, + 'skip_body_verify' => true, + } + verify(200, app_body, deflate_or_gzip, options) do |status, headers, body| + headers.must_equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + + buf = '' + raw_bytes = 0 + inflater = auto_inflater + body.each do |part| + raw_bytes += part.bytesize + buf << inflater.inflate(part) + end + buf << inflater.finish + expect = "hello\n" * 21 + buf.must_equal expect + raw_bytes.must_be(:<, expect.bytesize) + end + end end |