diff options
author | Jakub Pawlowicz <contact@jakubpawlowicz.com> | 2013-12-09 21:15:02 +0100 |
---|---|---|
committer | Jakub Pawlowicz <contact@jakubpawlowicz.com> | 2014-01-15 21:39:08 +0000 |
commit | 3ae2f3031b8925c641813734a5fa04c9bdafb137 (patch) | |
tree | 4a4680f9fbf7136f58096be85182cf315e4b6a9e | |
parent | 8e52002c3259223072b54bd040ff2f6a12b4d357 (diff) | |
download | rack-3ae2f3031b8925c641813734a5fa04c9bdafb137.tar.gz |
Adds deflater options to control compression on per-request level.
* Adds :if option which should be given a lambda accepting env, status, headers, and body options. * When :if evaluates to false a response body won't be compressed. * Adds :include option which should be given an array of compressible content types. * When :include don't include request's content type then response body won't be compressed.
-rw-r--r-- | lib/rack/deflater.rb | 39 | ||||
-rw-r--r-- | test/spec_deflater.rb | 83 |
2 files changed, 115 insertions, 7 deletions
diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb index fe2ac3db..638bf049 100644 --- a/lib/rack/deflater.rb +++ b/lib/rack/deflater.rb @@ -17,19 +17,26 @@ module Rack # directive of 'no-transform' is present, or when the response status # code is one that doesn't allow an entity body. class Deflater - def initialize(app) + ## + # Creates Rack::Deflater middleware. + # + # [app] rack app instance + # [options] hash of deflater options, i.e. + # 'if' - a lambda enabling / disabling deflation based on returned boolean value + # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 } + # 'include' - a list of content types that should be compressed + def initialize(app, options = {}) @app = app + + @condition = options[:if] + @compressible_types = options[:include] end def call(env) status, headers, body = @app.call(env) headers = Utils::HeaderHash.new(headers) - # Skip compressing empty entity body responses and responses with - # no-transform set. - if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || - headers['Cache-Control'].to_s =~ /\bno-transform\b/ || - (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/) + unless should_deflate?(env, status, headers, body) return [status, headers, body] end @@ -126,5 +133,25 @@ module Rack @body.close if @body.respond_to?(:close) end end + + private + + def should_deflate?(env, status, headers, body) + # Skip compressing empty entity body responses and responses with + # no-transform set. + if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Cache-Control'].to_s =~ /\bno-transform\b/ || + (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/) + return false + end + + # Skip if @compressible_types are given and does not include request's content type + return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/])) + + # Skip if @condition lambda is given and evaluates to false + return false if @condition && !@condition.call(env, status, headers, body) + + true + end end end diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb index 1dd57767..1e921eff 100644 --- a/test/spec_deflater.rb +++ b/test/spec_deflater.rb @@ -16,7 +16,7 @@ describe Rack::Deflater do end request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding)) - deflater = Rack::Lint.new Rack::Deflater.new(app) + deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {}) deflater.call(request) end @@ -34,6 +34,7 @@ describe Rack::Deflater do # 'app_body' - what body dummy app should return (may be changed by deflater at some point) # 'request_headers' - extra reqest headers to be sent # 'response_headers' - extra response headers to be returned + # 'deflater_options' - options passed to deflater middleware # [block] useful for doing some extra verification def verify(expected_status, expected_body, accept_encoding, options = {}, &block) accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash) @@ -255,4 +256,84 @@ describe Rack::Deflater do } verify(200, 'Hello World!', 'deflate', options) end + + should "deflate if content-type matches :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) + end + + should "deflate if content-type is included it :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain; charset=us-ascii' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) + end + + should "not deflate if content-type is not set but given in :include" do + options = { + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(304, 'Hello World!', { 'gzip' => nil }, options) + end + + should "not deflate if content-type do not match :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/json) + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end + + should "deflate response if :if lambda evaluates to true" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| true } + } + } + verify(200, 'Hello World!', 'deflate', options) + end + + should "not deflate if :if lambda evaluates to false" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| false } + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end + + should "check for Content-Length via :if" do + body = 'Hello World!' + body_len = body.length + options = { + 'response_headers' => { + 'Content-Length' => body_len.to_s + }, + 'deflater_options' => { + :if => lambda { |env, status, headers, body| + headers['Content-Length'].to_i >= body_len + } + } + } + + verify(200, body, 'gzip', options) + end end |