summary refs log tree commit
diff options
context:
space:
mode:
authorJakub Pawlowicz <contact@jakubpawlowicz.com>2013-12-09 21:15:02 +0100
committerJakub Pawlowicz <contact@jakubpawlowicz.com>2014-01-15 21:39:08 +0000
commit3ae2f3031b8925c641813734a5fa04c9bdafb137 (patch)
tree4a4680f9fbf7136f58096be85182cf315e4b6a9e
parent8e52002c3259223072b54bd040ff2f6a12b4d357 (diff)
downloadrack-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.rb39
-rw-r--r--test/spec_deflater.rb83
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