rack-cache-smash 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ .idea/*
19
+ *.iml
20
+ .ruby-version
21
+ vendor
22
+ *.wip.txt
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 1.0.0 / 2013-10-10
2
+
3
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-cache-smash.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rack'
8
+ gem 'rack-test'
9
+ gem 'timecop'
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Eliot Sykes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # rack-cache-smash
2
+
3
+ rack-cache-smash is a Rack middleware to cache bust **every** CSS and JS asset request, intended for development environments
4
+ only and not recommended for production.
5
+
6
+ Cache busting happens by modifying all HTML responses that contain paths to JS and CSS files. These file paths are appended
7
+ with a timestamp query string parameter to force browsers to re-download the files for every single page load as they always have a unique URL.
8
+
9
+ Admittedly, this is a sledgehammer of a middleware to overcome those browser caching issues that sometimes arise during development. I don't advise
10
+ using it in production, and I doubt that you want to.
11
+
12
+ At the time of writing this, I'm using rack-cache-smash during development to overcome a [problem in iOS 7.0.2 where incomplete asset downloads seem to be cached](http://tech.vg.no/2013/10/02/ios7-bug-shows-white-page-when-getting-304-not-modified-from-server/)
13
+ and some weirdness I've seen in IE10 on Windows 8 with assets, 304 Not Modified responses, and SSL.
14
+
15
+
16
+ ## Installation
17
+
18
+ Add the rack-cache-smash gem to the development group in your application's Gemfile:
19
+
20
+ group :development do
21
+ ...
22
+ gem 'rack-cache-smash'
23
+ end
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ For Rails apps, add this line to config/application.rb:
30
+
31
+ config.middleware.use(Rack::CacheSmash)
32
+
33
+ For Rack apps that don't use Rails, then place Rack::CacheSmash earlier in your middleware stack than the middleware(s) that generates response HTML.
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Run tests (`rake test`)
40
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
43
+
44
+ ## Releasing a new gem
45
+
46
+ 1. Increment version in lib/rack-cache-smash/version.rb in line with semantic versioning
47
+ 2. Update CHANGELOG.md
48
+ 3. Tests pass? (`rake test`)
49
+ 4. Build the gem (`rake build`)
50
+ 5. Release on rubygems.org (`rake release`)
51
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.pattern = 'test/**/*_test.rb'
6
+ test.verbose = true
7
+ end
@@ -0,0 +1,42 @@
1
+ require 'rack-cache-smash/version'
2
+
3
+ module Rack
4
+ class CacheSmash
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ response = @app.call(env)
12
+ headers = response[1]
13
+ return response unless html_response?(headers)
14
+
15
+ status = response.first
16
+ original_body_arr = response.last
17
+ body_str = cache_bust_asset_paths_in_body(original_body_arr)
18
+ return [status, headers, [body_str]]
19
+ end
20
+
21
+ private
22
+
23
+ PATHS_TO_CACHE_SMASH_REGEXP = /(?<ext>\.(?:js|css))(?:(?:\?)(?<query_string>.*?))?(?<end_quote>['"])/
24
+ HTML_CONTENT_TYPE_REGEXP = /\Atext\/html.*\z/
25
+
26
+ def html_response?(headers)
27
+ headers['Content-Type'] =~ HTML_CONTENT_TYPE_REGEXP
28
+ end
29
+
30
+ def cache_bust_asset_paths_in_body(original_body_arr)
31
+ body_str = ''
32
+ original_body_arr.each { |part| body_str << part }
33
+ timestamp = Time.now.utc.to_i
34
+ body_str.gsub!(PATHS_TO_CACHE_SMASH_REGEXP) do |match|
35
+ query_string_suffix = $~[:query_string] ? "&#{$~[:query_string]}" : ''
36
+ "#{$~[:ext]}?cachesmasher=#{timestamp}#{query_string_suffix}#{$~[:end_quote]}"
37
+ end
38
+ return body_str
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class CacheSmash
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack-cache-smash/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rack-cache-smash"
8
+ gem.version = Rack::CacheSmash::VERSION
9
+ gem.authors = ["Eliot Sykes"]
10
+ gem.email = ["e@jetbootlabs.com"]
11
+ gem.description = %q{Rack middleware to cache bust every CSS and JS asset request}
12
+ gem.summary = %q{Rack middleware to cache bust every CSS and JS asset request}
13
+ gem.homepage = "https://github.com/eliotsykes/rack-cache-smash"
14
+ gem.license = 'MIT'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,122 @@
1
+ require_relative 'test_helper'
2
+
3
+ class Rack::CacheSmashTest < Test::Unit::TestCase
4
+ include Rack::Test::Methods
5
+
6
+ def setup
7
+ @body = nil
8
+ @headers = {'Content-Type' => 'text/html'}
9
+ end
10
+
11
+ def app
12
+ status = 200
13
+ rack_app = lambda { |env| [status, @headers, @body] }
14
+ Rack::CacheSmash.new(rack_app)
15
+ end
16
+
17
+ def test_js_path_gets_cache_busted
18
+ assert_body_transform(
19
+ [
20
+ '<html><head>',
21
+ '<script src="/assets/application.js"></head><body>',
22
+ 'Hello</body></html>',
23
+ ],
24
+ "<html><head><script src=\"/assets/application.js?cachesmasher=[TIMESTAMP]\"></head><body>Hello</body></html>",
25
+ )
26
+ end
27
+
28
+ def test_css_path_gets_cache_busted
29
+ assert_body_transform(
30
+ [
31
+ '<html><head>',
32
+ "<link href='/assets/typography.css' rel='stylesheet' type='text/css'></head><body>",
33
+ 'Hello</body></html>',
34
+ ],
35
+ "<html><head><link href='/assets/typography.css?cachesmasher=[TIMESTAMP]' rel='stylesheet' type='text/css'></head><body>Hello</body></html>"
36
+ )
37
+ end
38
+
39
+ def test_js_path_with_query_string_gets_cache_busted
40
+ assert_body_transform(
41
+ [
42
+ '<html><head>',
43
+ '<script src="/assets/angular.js?body=1"></head><body>',
44
+ 'Hello</body></html>',
45
+ ],
46
+ "<html><head><script src=\"/assets/angular.js?cachesmasher=[TIMESTAMP]&body=1\"></head><body>Hello</body></html>",
47
+ )
48
+ end
49
+
50
+ def test_css_path_with_query_string_gets_cache_busted
51
+ assert_body_transform(
52
+ [
53
+ '<html><head>',
54
+ "<link href='/assets/layout.css?foo=bar&hello=world' rel='stylesheet' type='text/css'></head><body>",
55
+ 'Hello</body></html>',
56
+ ],
57
+ "<html><head><link href='/assets/layout.css?cachesmasher=[TIMESTAMP]&foo=bar&hello=world' rel='stylesheet' type='text/css'></head><body>Hello</body></html>"
58
+ )
59
+ end
60
+
61
+ def test_multiple_asset_paths_get_cache_busted
62
+ assert_body_transform(
63
+ [
64
+ '<html><head>',
65
+ "<script src='https://somewhere.tld/some/path/to/analytics.js'>",
66
+ "<link href='/assets/layout.css?foo=bar&hello=world' rel='stylesheet' type='text/css'>",
67
+ "<link href='/assets/footer.css' rel='stylesheet' type='text/css'></head><body>",
68
+ 'Hello<script type="text/javascript" src="jquery.js"><script type="text/javascript" src="plugin.js?woo=hoo"></body></html>'
69
+ ],
70
+ "<html><head><script src='https://somewhere.tld/some/path/to/analytics.js?cachesmasher=[TIMESTAMP]'>" +
71
+ "<link href='/assets/layout.css?cachesmasher=[TIMESTAMP]&foo=bar&hello=world' rel='stylesheet' type='text/css'>" +
72
+ "<link href='/assets/footer.css?cachesmasher=[TIMESTAMP]' rel='stylesheet' type='text/css'></head><body>" +
73
+ "Hello<script type=\"text/javascript\" src=\"jquery.js?cachesmasher=[TIMESTAMP]\">" +
74
+ "<script type=\"text/javascript\" src=\"plugin.js?cachesmasher=[TIMESTAMP]&woo=hoo\"></body></html>"
75
+ )
76
+ end
77
+
78
+ def test_does_not_modify_non_html_responses
79
+ @headers['Content-Type'] = 'text/plain'
80
+ @body = [
81
+ '<html><head>',
82
+ "<link href='/assets/typography.css' rel='stylesheet' type='text/css'></head><body>",
83
+ 'Hello</body></html>',
84
+ ]
85
+ unmodified_body_str = @body.join('')
86
+
87
+ get '/'
88
+
89
+ assert_equal unmodified_body_str, last_response.body
90
+ assert_equal 'text/plain', last_response.headers['content-type']
91
+ end
92
+
93
+ def test_handles_html_content_type_with_charset_specified
94
+ @headers['Content-Type'] = 'text/html; charset=utf-8'
95
+ assert_body_transform(
96
+ [
97
+ '<html><head>',
98
+ '<script src="/assets/application.js"></head><body>',
99
+ 'Hello</body></html>',
100
+ ],
101
+ "<html><head><script src=\"/assets/application.js?cachesmasher=[TIMESTAMP]\"></head><body>Hello</body></html>",
102
+ )
103
+ end
104
+
105
+ private
106
+
107
+ def assert_body_transform(original, result)
108
+ @body = original
109
+
110
+ Timecop.freeze do
111
+ get '/'
112
+
113
+ timestamp = Time.now.utc.to_i
114
+ assert_equal(
115
+ result.gsub('[TIMESTAMP]', timestamp.to_s),
116
+ last_response.body
117
+ )
118
+ assert_match /(text\/html)|(text\/html; charset=utf-8)/, last_response.headers['content-type']
119
+ end
120
+ end
121
+
122
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ require 'test/unit'
7
+ require 'rack/test'
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-cache-smash
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eliot Sykes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Rack middleware to cache bust every CSS and JS asset request
15
+ email:
16
+ - e@jetbootlabs.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - CHANGELOG.md
23
+ - Gemfile
24
+ - LICENSE
25
+ - README.md
26
+ - Rakefile
27
+ - lib/rack-cache-smash.rb
28
+ - lib/rack-cache-smash/version.rb
29
+ - rack-cache-smash.gemspec
30
+ - test/rack_cache_smash_test.rb
31
+ - test/test_helper.rb
32
+ homepage: https://github.com/eliotsykes/rack-cache-smash
33
+ licenses:
34
+ - MIT
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.23
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: Rack middleware to cache bust every CSS and JS asset request
57
+ test_files:
58
+ - test/rack_cache_smash_test.rb
59
+ - test/test_helper.rb