heroku_rails_deflate 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,8 +1,11 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem 'rack', '~> 1.4.5'
4
+ gem 'actionpack', '~> 3.2.13'
5
+ gem 'activesupport', '~> 3.2.13'
4
6
 
5
7
  group :development do
8
+ gem "rspec"
6
9
  gem "bundler"
7
10
  gem "jeweler"
8
11
  end
@@ -1,22 +1,66 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
+ actionpack (3.2.13)
5
+ activemodel (= 3.2.13)
6
+ activesupport (= 3.2.13)
7
+ builder (~> 3.0.0)
8
+ erubis (~> 2.7.0)
9
+ journey (~> 1.0.4)
10
+ rack (~> 1.4.5)
11
+ rack-cache (~> 1.2)
12
+ rack-test (~> 0.6.1)
13
+ sprockets (~> 2.2.1)
14
+ activemodel (3.2.13)
15
+ activesupport (= 3.2.13)
16
+ builder (~> 3.0.0)
17
+ activesupport (3.2.13)
18
+ i18n (= 0.6.1)
19
+ multi_json (~> 1.0)
20
+ builder (3.0.4)
21
+ diff-lcs (1.2.2)
22
+ erubis (2.7.0)
4
23
  git (1.2.5)
24
+ hike (1.2.1)
25
+ i18n (0.6.1)
5
26
  jeweler (1.8.4)
6
27
  bundler (~> 1.0)
7
28
  git (>= 1.2.5)
8
29
  rake
9
30
  rdoc
31
+ journey (1.0.4)
10
32
  json (1.7.7)
33
+ multi_json (1.7.2)
11
34
  rack (1.4.5)
35
+ rack-cache (1.2)
36
+ rack (>= 0.4)
37
+ rack-test (0.6.2)
38
+ rack (>= 1.0)
12
39
  rake (10.0.4)
13
40
  rdoc (4.0.1)
14
41
  json (~> 1.4)
42
+ rspec (2.13.0)
43
+ rspec-core (~> 2.13.0)
44
+ rspec-expectations (~> 2.13.0)
45
+ rspec-mocks (~> 2.13.0)
46
+ rspec-core (2.13.1)
47
+ rspec-expectations (2.13.0)
48
+ diff-lcs (>= 1.1.3, < 2.0)
49
+ rspec-mocks (2.13.0)
50
+ sprockets (2.2.2)
51
+ hike (~> 1.2)
52
+ multi_json (~> 1.0)
53
+ rack (~> 1.0)
54
+ tilt (~> 1.1, != 1.3.0)
55
+ tilt (1.3.6)
15
56
 
16
57
  PLATFORMS
17
58
  ruby
18
59
 
19
60
  DEPENDENCIES
61
+ actionpack (~> 3.2.13)
62
+ activesupport (~> 3.2.13)
20
63
  bundler
21
64
  jeweler
22
65
  rack (~> 1.4.5)
66
+ rspec
data/README.md CHANGED
@@ -1,6 +1,47 @@
1
1
  # heroku\_rails\_deflate
2
2
 
3
- Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku
3
+ The Heroku Cedar stack is not fronted by an asset server such as Varnish or nginx, and there is no automatic provision
4
+ for using gzip compression for HTTP transfers. At the same time, the Rails 3.2 asset pipeline spends a lot
5
+ of CPU cycles creating highly compressed versions of all our static assets. It would be great to use them!
6
+
7
+ This gem activates Rack::Deflate for all requests. But the real coolness is the custom middleware that checks
8
+ for the .gz version of precompiled assets and serves them up for you. We also eliminate conflict with Rack::Deflate
9
+ by telling it not to compress these already compressed files. We also provide a sensible default for the Cache-Control
10
+ header for these files. If you are using the asset digest in the filename (and you should), there is no reason
11
+ why we can't set very high max-age, so we set it to one year by default.
12
+
13
+ You should see a nice performance boost from the installation of this gem, without any additional work on your
14
+ part. After you get done with this, you could take it to the next level by adding a CDN such as AWS CloudFront.
15
+
16
+ ## Installation
17
+
18
+ * Add the gem to your app
19
+
20
+ ```ruby
21
+ gem 'heroku_rails_deflate', :group => :production
22
+ ```
23
+
24
+ * Make sure asset caching is configured correctly in environments/production.rb:
25
+
26
+ ```ruby
27
+ config.serve_static_assets = true
28
+ config.assets.compress = true
29
+ config.assets.compile = true
30
+ config.assets.digest = true
31
+ ```
32
+
33
+ * If you want a different max-age for your static assets, you can override the default:
34
+
35
+ ```ruby
36
+ config.static_cache_control = "public, max-age=31536000"
37
+ ```
38
+
39
+ * You should precompile your assets prior to deploying to Heroku to save CPU cycles at request time:
40
+
41
+ ```ruby
42
+ RAILS_ENV=production rake assets:precompile
43
+ ```
44
+
4
45
 
5
46
  ## Contributing to heroku\_rails\_deflate
6
47
 
@@ -12,6 +53,10 @@ Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku
12
53
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
54
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
55
 
56
+ ## Thanks
57
+
58
+ This gem expands on code originally published in a [gist](https://gist.github.com/guyboltonking/2152663) by [guyboltonking](https://github.com/guyboltonking).
59
+
15
60
  ## Copyright
16
61
 
17
62
  Copyright (c) 2013 Matt Olson. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -20,22 +20,13 @@ Jeweler::Tasks.new do |gem|
20
20
  gem.homepage = "http://github.com/mattolson/heroku_rails_deflate"
21
21
  gem.license = "MIT"
22
22
  gem.summary = %Q{Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku}
23
- gem.description = %Q{Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku. This allows us to take advantage of higher compression ratios of prezipped files, and reduces CPU load at request time.}
23
+ gem.description = %Q{This gem is designed for use by Rails applications running on Heroku. For others, the better approach is to use a frontend server such as nginx or Apache. However, the Heroku Cedar stack is no longer fronted by a file server, and there is no automatic provision for gzipping responses. This gem activates Rack::Deflate for all requests. In addition, we serve up the gzipped versions of our precompiled assets, taking advantage of the higher compression ratio used during precompilation, and reducing CPU load at request time.}
24
24
  gem.email = "matt@mattolson.com"
25
25
  gem.authors = ["Matt Olson"]
26
26
  # dependencies defined in Gemfile
27
27
  end
28
28
  Jeweler::RubygemsDotOrgTasks.new
29
29
 
30
- require 'rake/testtask'
31
- Rake::TestTask.new(:test) do |test|
32
- test.libs << 'lib' << 'test'
33
- test.pattern = 'test/**/test_*.rb'
34
- test.verbose = true
35
- end
36
-
37
- task :default => :test
38
-
39
30
  require 'rdoc/task'
40
31
  Rake::RDocTask.new do |rdoc|
41
32
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "heroku_rails_deflate"
8
- s.version = "0.2.1"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matt Olson"]
12
- s.date = "2013-04-02"
13
- s.description = "Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku. This allows us to take advantage of higher compression ratios of prezipped files, and reduces CPU load at request time."
12
+ s.date = "2013-04-03"
13
+ s.description = "This gem is designed for use by Rails applications running on Heroku. For others, the better approach is to use a frontend server such as nginx or Apache. However, the Heroku Cedar stack is no longer fronted by a file server, and there is no automatic provision for gzipping responses. This gem activates Rack::Deflate for all requests. In addition, we serve up the gzipped versions of our precompiled assets, taking advantage of the higher compression ratio used during precompilation, and reducing CPU load at request time."
14
14
  s.email = "matt@mattolson.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
@@ -28,8 +28,11 @@ Gem::Specification.new do |s|
28
28
  "lib/heroku_rails_deflate/railtie.rb",
29
29
  "lib/heroku_rails_deflate/serve_zipped_assets.rb",
30
30
  "lib/heroku_rails_deflate/version.rb",
31
- "test/helper.rb",
32
- "test/test_heroku_rails_deflate.rb"
31
+ "spec/fixtures/assets/bender.jpg",
32
+ "spec/fixtures/assets/bender.jpg.gz",
33
+ "spec/fixtures/non-assets/bender.jpg",
34
+ "spec/fixtures/non-assets/bender.jpg.gz",
35
+ "spec/serve_zipped_assets_spec.rb"
33
36
  ]
34
37
  s.homepage = "http://github.com/mattolson/heroku_rails_deflate"
35
38
  s.licenses = ["MIT"]
@@ -42,15 +45,24 @@ Gem::Specification.new do |s|
42
45
 
43
46
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
47
  s.add_runtime_dependency(%q<rack>, ["~> 1.4.5"])
48
+ s.add_runtime_dependency(%q<actionpack>, ["~> 3.2.13"])
49
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.2.13"])
50
+ s.add_development_dependency(%q<rspec>, [">= 0"])
45
51
  s.add_development_dependency(%q<bundler>, [">= 0"])
46
52
  s.add_development_dependency(%q<jeweler>, [">= 0"])
47
53
  else
48
54
  s.add_dependency(%q<rack>, ["~> 1.4.5"])
55
+ s.add_dependency(%q<actionpack>, ["~> 3.2.13"])
56
+ s.add_dependency(%q<activesupport>, ["~> 3.2.13"])
57
+ s.add_dependency(%q<rspec>, [">= 0"])
49
58
  s.add_dependency(%q<bundler>, [">= 0"])
50
59
  s.add_dependency(%q<jeweler>, [">= 0"])
51
60
  end
52
61
  else
53
62
  s.add_dependency(%q<rack>, ["~> 1.4.5"])
63
+ s.add_dependency(%q<actionpack>, ["~> 3.2.13"])
64
+ s.add_dependency(%q<activesupport>, ["~> 3.2.13"])
65
+ s.add_dependency(%q<rspec>, [">= 0"])
54
66
  s.add_dependency(%q<bundler>, [">= 0"])
55
67
  s.add_dependency(%q<jeweler>, [">= 0"])
56
68
  end
@@ -1,23 +1,14 @@
1
+ require 'action_controller'
2
+ require 'active_support/core_ext/uri'
1
3
  require 'action_dispatch/middleware/static'
2
4
 
3
5
  # Adapted from https://gist.github.com/guyboltonking/2152663
4
6
  module HerokuRailsDeflate
5
- # Rails static middleware, but restricted to assets path
6
- class FileHandler < ActionDispatch::FileHandler
7
- def initialize(root, assets_path, cache_control)
8
- @assets_path = assets_path.chomp('/') + '/'
9
- super(root, cache_control)
10
- end
11
-
12
- def match?(path)
13
- path.start_with?(@assets_path) && super(path)
14
- end
15
- end
16
-
17
7
  class ServeZippedAssets
18
- def initialize(app, path, assets_path, cache_control=nil)
8
+ def initialize(app, root, assets_path, cache_control=nil)
19
9
  @app = app
20
- @file_handler = FileHandler.new(path, assets_path, cache_control)
10
+ @assets_path = assets_path.chomp('/') + '/'
11
+ @file_handler = ActionDispatch::FileHandler.new(root, cache_control)
21
12
  end
22
13
 
23
14
  def call(env)
@@ -25,37 +16,37 @@ module HerokuRailsDeflate
25
16
  request = Rack::Request.new(env)
26
17
  encoding = Rack::Utils.select_best_encoding(%w(gzip identity), request.accept_encoding)
27
18
 
28
- if encoding == 'gzip'
29
- # See if gzipped version exists in assets directory
30
- pathgz = env['PATH_INFO'] + '.gz'
31
- if match = @file_handler.match?(pathgz)
32
- # Get the filehandler to serve up the gzipped file, then strip the .gz suffix
33
- env["PATH_INFO"] = match
34
- status, headers, body = @file_handler.call(env)
35
- path = env["PATH_INFO"] = env["PATH_INFO"].chomp('.gz')
36
-
37
- # Set the Vary HTTP header.
38
- vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
39
- unless vary.include?("*") || vary.include?("Accept-Encoding")
40
- headers["Vary"] = vary.push("Accept-Encoding").join(",")
41
- end
42
-
43
- # Add encoding and type
44
- headers['Content-Encoding'] = 'gzip'
45
- headers['Content-Type'] = Rack::Mime.mime_type(File.extname(path), 'text/plain')
46
- headers.delete('Content-Length')
47
-
48
- # Update cache-control to add directive telling Rack::Deflate to leave it alone.
49
- cache_control = headers['Cache-Control'].try(:to_s).try(:downcase)
50
- if cache_control.nil?
51
- headers['Cache-Control'] = 'no-transform'
52
- elsif !cache_control.include?('no-transform')
53
- headers['Cache-Control'] += ', no-transform'
54
- end
19
+ if encoding == 'gzip'
20
+ # See if gzipped version exists in assets directory
21
+ compressed_path = env['PATH_INFO'] + '.gz'
22
+ if compressed_path.start_with?(@assets_path) && (match = @file_handler.match?(compressed_path))
23
+ # Get the FileHandler to serve up the gzipped file, then strip the .gz suffix
24
+ env["PATH_INFO"] = match
25
+ status, headers, body = @file_handler.call(env)
26
+ path = env["PATH_INFO"] = env["PATH_INFO"].chomp('.gz')
27
+
28
+ # Set the Vary HTTP header.
29
+ vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
30
+ unless vary.include?("*") || vary.include?("Accept-Encoding")
31
+ headers["Vary"] = vary.push("Accept-Encoding").join(",")
32
+ end
55
33
 
56
- return [status, headers, body]
34
+ # Add encoding and type
35
+ headers['Content-Encoding'] = 'gzip'
36
+ headers['Content-Type'] = Rack::Mime.mime_type(File.extname(path), 'text/plain')
37
+ headers.delete('Content-Length')
38
+
39
+ # Update cache-control to add directive telling Rack::Deflate to leave it alone.
40
+ cache_control = headers['Cache-Control'].try(:to_s).try(:downcase)
41
+ if cache_control.nil?
42
+ headers['Cache-Control'] = 'no-transform'
43
+ elsif !cache_control.include?('no-transform')
44
+ headers['Cache-Control'] += ', no-transform'
57
45
  end
46
+
47
+ return [status, headers, body]
58
48
  end
49
+ end
59
50
  end
60
51
 
61
52
  @app.call(env)
@@ -1,8 +1,8 @@
1
1
  module HerokuRailsDeflate
2
2
  class Version
3
- MAJOR = 0
4
- MINOR = 2
5
- PATCH = 1
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 0
6
6
  BUILD = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
@@ -0,0 +1,51 @@
1
+ require 'rack/mock'
2
+ require 'rack/static'
3
+ require 'heroku_rails_deflate/serve_zipped_assets'
4
+
5
+ # Return a bogus response so we can detect rack passthrough
6
+ class MockServer
7
+ def initialize; end
8
+ def call(env)
9
+ Rack::MockResponse.new(500, {'X-mock' => 'mocked!'}, 'ERROR')
10
+ end
11
+ end
12
+
13
+ describe HerokuRailsDeflate::ServeZippedAssets do
14
+ def process(path, cache_control='public, max-age=31536000')
15
+ root_path = File.expand_path('../fixtures', __FILE__)
16
+ request_env = Rack::MockRequest.env_for(path)
17
+ request_env['HTTP_ACCEPT_ENCODING'] = 'compress, gzip, deflate'
18
+
19
+ deflate_server = described_class.new(MockServer.new, root_path, '/assets', cache_control)
20
+ deflate_server.call(request_env)
21
+ end
22
+
23
+ it "has correct content encoding" do
24
+ status, headers, body = process('/assets/bender.jpg')
25
+ status.should eq(200)
26
+ headers['Content-Encoding'].should eq('gzip')
27
+ end
28
+
29
+ it "has correct cache-control header" do
30
+ status, headers, body = process('/assets/bender.jpg')
31
+ status.should eq(200)
32
+ headers['Cache-Control'].should eq('public, max-age=31536000, no-transform')
33
+ end
34
+
35
+ it "should not modify existing cache-control header" do
36
+ status, headers, body = process('/assets/bender.jpg', 'private')
37
+ status.should eq(200)
38
+ headers['Cache-Control'].should eq('private, no-transform')
39
+ end
40
+
41
+ it "should create cache-control header if necessary" do
42
+ status, headers, body = process('/assets/bender.jpg', nil)
43
+ status.should eq(200)
44
+ headers['Cache-Control'].should eq('no-transform')
45
+ end
46
+
47
+ it "should not serve anything from non-asset directories" do
48
+ status, headers, body = process('/non-asset/bender.jpg', nil)
49
+ headers['X-mock'].should eq('mocked!')
50
+ end
51
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_rails_deflate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-02 00:00:00.000000000 Z
12
+ date: 2013-04-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -27,6 +27,54 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: 1.4.5
30
+ - !ruby/object:Gem::Dependency
31
+ name: actionpack
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.2.13
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 3.2.13
46
+ - !ruby/object:Gem::Dependency
47
+ name: activesupport
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 3.2.13
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.2.13
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
30
78
  - !ruby/object:Gem::Dependency
31
79
  name: bundler
32
80
  requirement: !ruby/object:Gem::Requirement
@@ -59,9 +107,13 @@ dependencies:
59
107
  - - ! '>='
60
108
  - !ruby/object:Gem::Version
61
109
  version: '0'
62
- description: Activate Rack::Deflate and serve up precompiled, gzipped assets on Heroku.
63
- This allows us to take advantage of higher compression ratios of prezipped files,
64
- and reduces CPU load at request time.
110
+ description: This gem is designed for use by Rails applications running on Heroku.
111
+ For others, the better approach is to use a frontend server such as nginx or Apache.
112
+ However, the Heroku Cedar stack is no longer fronted by a file server, and there
113
+ is no automatic provision for gzipping responses. This gem activates Rack::Deflate
114
+ for all requests. In addition, we serve up the gzipped versions of our precompiled
115
+ assets, taking advantage of the higher compression ratio used during precompilation,
116
+ and reducing CPU load at request time.
65
117
  email: matt@mattolson.com
66
118
  executables: []
67
119
  extensions: []
@@ -80,8 +132,11 @@ files:
80
132
  - lib/heroku_rails_deflate/railtie.rb
81
133
  - lib/heroku_rails_deflate/serve_zipped_assets.rb
82
134
  - lib/heroku_rails_deflate/version.rb
83
- - test/helper.rb
84
- - test/test_heroku_rails_deflate.rb
135
+ - spec/fixtures/assets/bender.jpg
136
+ - spec/fixtures/assets/bender.jpg.gz
137
+ - spec/fixtures/non-assets/bender.jpg
138
+ - spec/fixtures/non-assets/bender.jpg.gz
139
+ - spec/serve_zipped_assets_spec.rb
85
140
  homepage: http://github.com/mattolson/heroku_rails_deflate
86
141
  licenses:
87
142
  - MIT
@@ -97,7 +152,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
152
  version: '0'
98
153
  segments:
99
154
  - 0
100
- hash: -257709888069161543
155
+ hash: 848461724416935905
101
156
  required_rubygems_version: !ruby/object:Gem::Requirement
102
157
  none: false
103
158
  requirements:
@@ -1,18 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'test/unit'
11
- require 'shoulda'
12
-
13
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
- $LOAD_PATH.unshift(File.dirname(__FILE__))
15
- require 'heroku_rails_deflate'
16
-
17
- class Test::Unit::TestCase
18
- end
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestHerokuRailsDeflate < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end