action_dispatch-gz_static 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +3 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/action_dispatch-gz_static.gemspec +18 -0
  6. data/lib/action_dispatch/gz_railtie.rb +9 -0
  7. data/lib/action_dispatch/gz_static.rb +81 -0
  8. data/test/gz_static_test.rb +179 -0
  9. data/test/public/.gitignore +1 -0
  10. data/test/public/404.html +1 -0
  11. data/test/public/500.da.html +1 -0
  12. data/test/public/500.html +1 -0
  13. data/test/public/compressed.html +1 -0
  14. data/test/public/compressed.html.gz +0 -0
  15. data/test/public/elsewhere/cools.js +1 -0
  16. data/test/public/elsewhere/file.css +1 -0
  17. data/test/public/foo/bar.html +1 -0
  18. data/test/public/foo/baz.css +3 -0
  19. data/test/public/foo/index.html +1 -0
  20. data/test/public/foo//343/201/223/343/202/223/343/201/253/343/201/241/343/201/257.html +1 -0
  21. data/test/public/images/rails.png +0 -0
  22. data/test/public/index.html +1 -0
  23. data/test/public/javascripts/application.js +1 -0
  24. data/test/public/javascripts/bank.js +1 -0
  25. data/test/public/javascripts/common.javascript +1 -0
  26. data/test/public/javascripts/controls.js +1 -0
  27. data/test/public/javascripts/dragdrop.js +1 -0
  28. data/test/public/javascripts/effects.js +1 -0
  29. data/test/public/javascripts/prototype.js +1 -0
  30. data/test/public/javascripts/robber.js +1 -0
  31. data/test/public/javascripts/subdir/subdir.js +1 -0
  32. data/test/public/javascripts/version.1.0.js +1 -0
  33. data/test/public/stylesheets/bank.css +1 -0
  34. data/test/public/stylesheets/random.styles +1 -0
  35. data/test/public/stylesheets/robber.css +1 -0
  36. data/test/public/stylesheets/subdir/subdir.css +1 -0
  37. data/test/public/stylesheets/version.1.0.css +1 -0
  38. metadata +142 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # ActionDispatch::GzStatic
2
+
3
+ ActionDispatch::GzStatic is a derivative of ActionDispatch::Static, that has
4
+ been modified to serve the .gz files that are created by the Rails asset
5
+ precompiler.
6
+
7
+ If you are using Rails to serve static assets in production, and
8
+ you are using the asset precompiler, then it is probably a good idea to use this
9
+ gem. If you running on Heroku's Cedar stack, this includes you.
10
+
11
+ ActionDispatch::GzStatic is a better solution that using Rack::Deflater on your
12
+ static assets, because this has the undesirable side effect of recompressing
13
+ assets that are already compressed, such as images.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'action_dispatch-gz_static'
20
+
21
+ Add this to application.rb:
22
+
23
+ # The railtie initializer will look for ActionDispatch::Static, and swap it
24
+ # with ActionDispatch::GzStatic
25
+ config.serve_static_assets = true
26
+
27
+ # This isn't required, but is generally a good idea.
28
+ config.static_cache_control = "public, max-age=#{1.month.to_i}"
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.name = "action_dispatch-gz_static"
4
+ gem.version = '0.0.1'
5
+ gem.authors = ["Jonathan Baudanza"]
6
+ gem.email = ["jon@jonb.org"]
7
+ gem.description = %q{Serves the .gz files that are created by the asset precompiler}
8
+ gem.summary = %q{Serves the .gz files that are created by the asset precompiler}
9
+ gem.homepage = "https://github.com/jbaudanza/action_dispatch-gz_static"
10
+
11
+ gem.add_dependency('actionpack', '>= 3.1.0', '< 4')
12
+ gem.add_dependency('railties', '>= 3.1.0', '< 4')
13
+
14
+ gem.files = `git ls-files`.split($/).collect{ |str| str[0] == '"' ? eval(str) : str }
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ end
@@ -0,0 +1,9 @@
1
+ module ActionDispatch
2
+ class GzRailtie < Rails::Railtie
3
+ initializer "gz_static.install_middleware" do |app|
4
+ app.middleware.swap ::ActionDispatch::Static, ::ActionDispatch::GzStatic,
5
+ app.paths['public'].first,
6
+ app.config.static_cache_control
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+ require 'rack/file'
5
+ require 'active_support/core_ext/uri'
6
+
7
+ require 'action_dispatch/gz_railtie' if defined?(Rails)
8
+
9
+ module ActionDispatch
10
+ class GzStatic
11
+ def initialize(app, root, cache_control=nil)
12
+ @app = app
13
+ @root = root.chomp('/')
14
+ @compiled_root = /^#{Regexp.escape(@root)}/
15
+ @file_server = ::Rack::File.new(@root, cache_control)
16
+ end
17
+
18
+ def match?(path)
19
+ path = path.dup
20
+
21
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
22
+ paths = "#{full_path}#{ext}"
23
+
24
+ matches = Dir[paths]
25
+ matches.detect { |m| File.file?(m) }
26
+ end
27
+
28
+ def call(env)
29
+ case env['REQUEST_METHOD']
30
+ when 'GET', 'HEAD'
31
+ path = env['PATH_INFO'].chomp('/')
32
+ if match = match?(path)
33
+
34
+ compressed_match = "#{match}.gz"
35
+ compressed_exists = File.file?(compressed_match)
36
+
37
+ wants_compressed = !!(env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/)
38
+
39
+ if wants_compressed && compressed_exists
40
+ mime = Rack::Mime.mime_type(::File.extname(match), 'text/plain')
41
+ match = compressed_match
42
+ end
43
+
44
+ match.sub!(@compiled_root, '')
45
+ env["PATH_INFO"] = ::Rack::Utils.escape(match)
46
+ status, headers, body = @file_server.call(env)
47
+
48
+ if compressed_exists
49
+ headers['Vary'] = 'Accept-Encoding'
50
+
51
+ if wants_compressed
52
+ headers['Content-Encoding'] = 'gzip'
53
+ headers['Content-Type'] = mime if mime
54
+ end
55
+ end
56
+
57
+ return [status, headers, body]
58
+ end
59
+ end
60
+
61
+ @app.call(env)
62
+ end
63
+
64
+ def ext
65
+ @ext ||= begin
66
+ #ext = ::ActionController::Base.default_static_extension
67
+ ext = ".html"
68
+ "{,#{ext},/index#{ext}}"
69
+ end
70
+ end
71
+
72
+ def unescape_path(path)
73
+ URI.parser.unescape(path)
74
+ end
75
+
76
+ def escape_glob_chars(path)
77
+ path.force_encoding('binary') if path.respond_to? :force_encoding
78
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+ require 'minitest/autorun'
3
+ require 'rbconfig'
4
+ require 'active_support'
5
+ require 'action_dispatch/gz_static'
6
+ require 'rack/mock'
7
+ require 'zlib'
8
+
9
+ PUBLIC_LOAD_PATH = File.join(File.dirname(__FILE__), 'public')
10
+
11
+ module StaticTests
12
+ def test_serves_dynamic_content
13
+ assert_equal "Hello, World!", get("/nofile").body
14
+ end
15
+
16
+ def test_handles_urls_with_bad_encoding
17
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
18
+ end
19
+
20
+ def test_sets_cache_control
21
+ response = get("/index.html")
22
+ assert_html "/index.html", response
23
+ assert_equal "public, max-age=60", response.headers["Cache-Control"]
24
+ end
25
+
26
+ def test_serves_static_index_at_root
27
+ assert_html "/index.html", get("/index.html")
28
+ assert_html "/index.html", get("/index")
29
+ assert_html "/index.html", get("/")
30
+ assert_html "/index.html", get("")
31
+ end
32
+
33
+ def test_serves_static_file_in_directory
34
+ assert_html "/foo/bar.html", get("/foo/bar.html")
35
+ assert_html "/foo/bar.html", get("/foo/bar/")
36
+ assert_html "/foo/bar.html", get("/foo/bar")
37
+ end
38
+
39
+ def test_serves_static_index_file_in_directory
40
+ assert_html "/foo/index.html", get("/foo/index.html")
41
+ assert_html "/foo/index.html", get("/foo/")
42
+ assert_html "/foo/index.html", get("/foo")
43
+ end
44
+
45
+ def test_served_static_file_with_non_english_filename
46
+ assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
47
+ end
48
+
49
+
50
+ def test_serves_static_file_with_exclamation_mark_in_filename
51
+ with_static_file "/foo/foo!bar.html" do |file|
52
+ assert_html file, get("/foo/foo%21bar.html")
53
+ assert_html file, get("/foo/foo!bar.html")
54
+ end
55
+ end
56
+
57
+ def test_serves_static_file_with_dollar_sign_in_filename
58
+ with_static_file "/foo/foo$bar.html" do |file|
59
+ assert_html file, get("/foo/foo%24bar.html")
60
+ assert_html file, get("/foo/foo$bar.html")
61
+ end
62
+ end
63
+
64
+ def test_serves_static_file_with_ampersand_in_filename
65
+ with_static_file "/foo/foo&bar.html" do |file|
66
+ assert_html file, get("/foo/foo%26bar.html")
67
+ assert_html file, get("/foo/foo&bar.html")
68
+ end
69
+ end
70
+
71
+ def test_serves_static_file_with_apostrophe_in_filename
72
+ with_static_file "/foo/foo'bar.html" do |file|
73
+ assert_html file, get("/foo/foo%27bar.html")
74
+ assert_html file, get("/foo/foo'bar.html")
75
+ end
76
+ end
77
+
78
+ def test_serves_static_file_with_parentheses_in_filename
79
+ with_static_file "/foo/foo(bar).html" do |file|
80
+ assert_html file, get("/foo/foo%28bar%29.html")
81
+ assert_html file, get("/foo/foo(bar).html")
82
+ end
83
+ end
84
+
85
+ def test_serves_static_file_with_plus_sign_in_filename
86
+ with_static_file "/foo/foo+bar.html" do |file|
87
+ assert_html file, get("/foo/foo%2Bbar.html")
88
+ assert_html file, get("/foo/foo+bar.html")
89
+ end
90
+ end
91
+
92
+ def test_serves_static_file_with_comma_in_filename
93
+ with_static_file "/foo/foo,bar.html" do |file|
94
+ assert_html file, get("/foo/foo%2Cbar.html")
95
+ assert_html file, get("/foo/foo,bar.html")
96
+ end
97
+ end
98
+
99
+ def test_serves_static_file_with_semi_colon_in_filename
100
+ with_static_file "/foo/foo;bar.html" do |file|
101
+ assert_html file, get("/foo/foo%3Bbar.html")
102
+ assert_html file, get("/foo/foo;bar.html")
103
+ end
104
+ end
105
+
106
+ def test_serves_static_file_with_at_symbol_in_filename
107
+ with_static_file "/foo/foo@bar.html" do |file|
108
+ assert_html file, get("/foo/foo%40bar.html")
109
+ assert_html file, get("/foo/foo@bar.html")
110
+ end
111
+ end
112
+
113
+ def test_serves_uncompressed_file
114
+ response = get('/compressed.html')
115
+ assert_equal 200, response.status
116
+ assert_html "Hello, static file", response
117
+ assert_equal "Accept-Encoding", response.headers['Vary']
118
+ assert_nil response.headers['Content-Encoding']
119
+ end
120
+
121
+ def test_serves_compressed_file
122
+ response = get('/compressed.html', 'HTTP_ACCEPT_ENCODING' => 'gzip')
123
+ assert_equal 200, response.status
124
+ gz = Zlib::GzipReader.new(StringIO.new(response.body))
125
+ assert_equal "Hello, static file", gz.read
126
+ assert_equal "Accept-Encoding", response.headers['Vary']
127
+ assert_equal "gzip", response.headers['Content-Encoding']
128
+ assert_equal "text/html", response.headers['Content-Type']
129
+ end
130
+
131
+ # Windows doesn't allow \ / : * ? " < > | in filenames
132
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
133
+ def test_serves_static_file_with_colon
134
+ with_static_file "/foo/foo:bar.html" do |file|
135
+ assert_html file, get("/foo/foo%3Abar.html")
136
+ assert_html file, get("/foo/foo:bar.html")
137
+ end
138
+ end
139
+
140
+ def test_serves_static_file_with_asterisk
141
+ with_static_file "/foo/foo*bar.html" do |file|
142
+ assert_html file, get("/foo/foo%2Abar.html")
143
+ assert_html file, get("/foo/foo*bar.html")
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def assert_html(body, response)
151
+ assert_equal body, response.body
152
+ assert_equal "text/html", response.headers["Content-Type"]
153
+ end
154
+
155
+ def get(path, opts={})
156
+ Rack::MockRequest.new(@app).request("GET", path, opts)
157
+ end
158
+
159
+ def with_static_file(file)
160
+ path = PUBLIC_LOAD_PATH + file
161
+ File.open(path, "wb+") { |f| f.write(file) }
162
+ yield file
163
+ ensure
164
+ File.delete(path)
165
+ end
166
+ end
167
+
168
+ class StaticTest < ActiveSupport::TestCase
169
+ DummyApp = lambda { |env|
170
+ [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
171
+ }
172
+ App = ActionDispatch::GzStatic.new(DummyApp, PUBLIC_LOAD_PATH, "public, max-age=60")
173
+
174
+ def setup
175
+ @app = App
176
+ end
177
+
178
+ include StaticTests
179
+ end
@@ -0,0 +1 @@
1
+ absolute/*
@@ -0,0 +1 @@
1
+ 404 error fixture
@@ -0,0 +1 @@
1
+ 500 localized error fixture
@@ -0,0 +1 @@
1
+ 500 error fixture
@@ -0,0 +1 @@
1
+ Hello, static file
Binary file
@@ -0,0 +1 @@
1
+ // cools.js
@@ -0,0 +1 @@
1
+ /*file.css*/
@@ -0,0 +1 @@
1
+ /foo/bar.html
@@ -0,0 +1,3 @@
1
+ body {
2
+ background: #000;
3
+ }
@@ -0,0 +1 @@
1
+ /foo/index.html
Binary file
@@ -0,0 +1 @@
1
+ /index.html
@@ -0,0 +1 @@
1
+ // application js
@@ -0,0 +1 @@
1
+ // bank js
@@ -0,0 +1 @@
1
+ // common.javascript
@@ -0,0 +1 @@
1
+ // controls js
@@ -0,0 +1 @@
1
+ // dragdrop js
@@ -0,0 +1 @@
1
+ // effects js
@@ -0,0 +1 @@
1
+ // prototype js
@@ -0,0 +1 @@
1
+ // robber js
@@ -0,0 +1 @@
1
+ // subdir js
@@ -0,0 +1 @@
1
+ // version.1.0 js
@@ -0,0 +1 @@
1
+ /* bank.css */
@@ -0,0 +1 @@
1
+ /* random.styles */
@@ -0,0 +1 @@
1
+ /* robber.css */
@@ -0,0 +1 @@
1
+ /* subdir.css */
@@ -0,0 +1 @@
1
+ /* version.1.0.css */
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_dispatch-gz_static
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jonathan Baudanza
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-27 00:00:00.000000000 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: actionpack
17
+ requirement: &2160710380 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ - - <
24
+ - !ruby/object:Gem::Version
25
+ version: '4'
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: *2160710380
29
+ - !ruby/object:Gem::Dependency
30
+ name: railties
31
+ requirement: &2160708340 !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: 3.1.0
37
+ - - <
38
+ - !ruby/object:Gem::Version
39
+ version: '4'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: *2160708340
43
+ description: Serves the .gz files that are created by the asset precompiler
44
+ email:
45
+ - jon@jonb.org
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - .gitignore
51
+ - Gemfile
52
+ - README.md
53
+ - Rakefile
54
+ - action_dispatch-gz_static.gemspec
55
+ - lib/action_dispatch/gz_railtie.rb
56
+ - lib/action_dispatch/gz_static.rb
57
+ - test/gz_static_test.rb
58
+ - test/public/.gitignore
59
+ - test/public/404.html
60
+ - test/public/500.da.html
61
+ - test/public/500.html
62
+ - test/public/compressed.html
63
+ - test/public/compressed.html.gz
64
+ - test/public/elsewhere/cools.js
65
+ - test/public/elsewhere/file.css
66
+ - test/public/foo/bar.html
67
+ - test/public/foo/baz.css
68
+ - test/public/foo/index.html
69
+ - test/public/foo/こんにちは.html
70
+ - test/public/images/rails.png
71
+ - test/public/index.html
72
+ - test/public/javascripts/application.js
73
+ - test/public/javascripts/bank.js
74
+ - test/public/javascripts/common.javascript
75
+ - test/public/javascripts/controls.js
76
+ - test/public/javascripts/dragdrop.js
77
+ - test/public/javascripts/effects.js
78
+ - test/public/javascripts/prototype.js
79
+ - test/public/javascripts/robber.js
80
+ - test/public/javascripts/subdir/subdir.js
81
+ - test/public/javascripts/version.1.0.js
82
+ - test/public/stylesheets/bank.css
83
+ - test/public/stylesheets/random.styles
84
+ - test/public/stylesheets/robber.css
85
+ - test/public/stylesheets/subdir/subdir.css
86
+ - test/public/stylesheets/version.1.0.css
87
+ has_rdoc: true
88
+ homepage: https://github.com/jbaudanza/action_dispatch-gz_static
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.6.2
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Serves the .gz files that are created by the asset precompiler
112
+ test_files:
113
+ - test/gz_static_test.rb
114
+ - test/public/.gitignore
115
+ - test/public/404.html
116
+ - test/public/500.da.html
117
+ - test/public/500.html
118
+ - test/public/compressed.html
119
+ - test/public/compressed.html.gz
120
+ - test/public/elsewhere/cools.js
121
+ - test/public/elsewhere/file.css
122
+ - test/public/foo/bar.html
123
+ - test/public/foo/baz.css
124
+ - test/public/foo/index.html
125
+ - test/public/foo/こんにちは.html
126
+ - test/public/images/rails.png
127
+ - test/public/index.html
128
+ - test/public/javascripts/application.js
129
+ - test/public/javascripts/bank.js
130
+ - test/public/javascripts/common.javascript
131
+ - test/public/javascripts/controls.js
132
+ - test/public/javascripts/dragdrop.js
133
+ - test/public/javascripts/effects.js
134
+ - test/public/javascripts/prototype.js
135
+ - test/public/javascripts/robber.js
136
+ - test/public/javascripts/subdir/subdir.js
137
+ - test/public/javascripts/version.1.0.js
138
+ - test/public/stylesheets/bank.css
139
+ - test/public/stylesheets/random.styles
140
+ - test/public/stylesheets/robber.css
141
+ - test/public/stylesheets/subdir/subdir.css
142
+ - test/public/stylesheets/version.1.0.css