Skip to content

Commit

Permalink
📂 Add option for gzip_compression_level for Rack::Deflater.
Browse files Browse the repository at this point in the history
`:gzip_compression_level ::` An integer from 0 to 9 controlling the
level of compression; 1 is fastest and produces the least compression,
and 9 is slowest and produces the most compression. 0 is no compression.
The default is 6. This is only needed for the `gzip` type encoding.
  • Loading branch information
bayleedev committed Jan 27, 2021
1 parent a05f8d5 commit 72e1824
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. For info on
### Added

- `Rack::RewindableInput` supports size. ([@ahorek](https://github.com/ahorek))
- `Rack::Deflater` supports `gzip_compression_level`. ([@bayleedev](https://github.com/bayleedev))

### Changed

Expand Down
15 changes: 12 additions & 3 deletions lib/rack/deflater.rb
Expand Up @@ -33,11 +33,18 @@ class Deflater
# :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces
# latency for time-sensitive streaming applications, but hurts compression and throughput.
# Defaults to +true+.
# :gzip_compression_level :: An integer from 0 to 9 controlling the level of
# compression; 1 is fastest and produces the least
# compression, and 9 is slowest and produces the
# most compression. 0 is no compression. The
# default is 6. This is only needed for the `gzip`
# type encoding.
def initialize(app, options = {})
@app = app
@condition = options[:if]
@compressible_types = options[:include]
@sync = options.fetch(:sync, true)
@gzip_compression_level = options.fetch(:gzip_compression_level, 6)
end

def call(env)
Expand Down Expand Up @@ -65,7 +72,7 @@ def call(env)
headers.delete(CONTENT_LENGTH)
mtime = headers["Last-Modified"]
mtime = Time.httpdate(mtime).to_i if mtime
[status, headers, GzipStream.new(body, mtime, @sync)]
[status, headers, GzipStream.new(body, mtime, @sync, @gzip_compression_level)]
when "identity"
[status, headers, body]
when nil
Expand All @@ -82,16 +89,18 @@ class GzipStream
# mtime :: The modification time of the body, used to set the
# modification time in the gzip header.
# sync :: Whether to flush each gzip chunk as soon as it is ready.
def initialize(body, mtime, sync)
# gzip_compression_level :: The compression value for gzip.
def initialize(body, mtime, sync, gzip_compression_level)
@body = body
@mtime = mtime
@sync = sync
@gzip_compression_level = gzip_compression_level
end

# Yield gzip compressed strings to the given block.
def each(&block)
@writer = block
gzip = ::Zlib::GzipWriter.new(self)
gzip = ::Zlib::GzipWriter.new(self, @gzip_compression_level)
gzip.mtime = @mtime if @mtime
@body.each { |part|
# Skip empty strings, as they would result in no output,
Expand Down
25 changes: 25 additions & 0 deletions test/spec_deflater.rb
Expand Up @@ -402,6 +402,31 @@ class << app_body; def each; yield('foo'); yield('bar'); end; end
verify(200, response, 'gzip', options)
end

it 'will change gzip level' do
response = 'Hello World!' * 1000
small_options = {
'deflater_options' => { gzip_compression_level: 9 },
'skip_body_verify' => true,
}
fast_options = {
'deflater_options' => { gzip_compression_level: 1 },
'skip_body_verify' => true,
}
verify(200, response, 'gzip', small_options) do |_, _, body_small|
verify(200, response, 'gzip', fast_options) do |_, _, body_fast|
small_bytes = 0
body_small.each do |part|
small_bytes += part.bytesize
end
fast_bytes = 0
body_fast.each do |part|
fast_bytes += part.bytesize
end
assert small_bytes < fast_bytes
end
end
end

it 'will honor sync: false to avoid unnecessary flushing' do
app_body = Object.new
class << app_body
Expand Down

0 comments on commit 72e1824

Please sign in to comment.