static_signature 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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +60 -0
- data/Rakefile +2 -0
- data/lib/static_signature.rb +56 -0
- data/lib/static_signature/version.rb +3 -0
- data/static_signature.gemspec +21 -0
- data/test/data/css/app.css +3 -0
- data/test/data/js/app.js +1 -0
- data/test/middleware_test.rb +61 -0
- data/test/test_helper.rb +6 -0
- metadata +86 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Brendon Murphy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# StaticSignature
|
2
|
+
|
3
|
+
Rack Middleware for adding query string cache busting signatures on
|
4
|
+
script and style assets. This is accomplished by parsing html responses
|
5
|
+
and appending an md5 hexdigest of file contents to the querystring.
|
6
|
+
|
7
|
+
In other words it turns this:
|
8
|
+
```html
|
9
|
+
<link href="/stylesheets/screen.css" rel="stylesheet">
|
10
|
+
```
|
11
|
+
|
12
|
+
into this:
|
13
|
+
```html
|
14
|
+
<link href="/stylesheets/screen.css?4a75c99cdc76e7e0f3f3a0d6a44338a4" rel="stylesheet">
|
15
|
+
```
|
16
|
+
|
17
|
+
This is intended to be a very limited implementation and makes a few
|
18
|
+
assumptions:
|
19
|
+
|
20
|
+
* You are OK with Nokogiri parsing and modifying all text/html responses
|
21
|
+
* The markup all uses simple urls that don't already have query params or
|
22
|
+
such appended already
|
23
|
+
* Query params style cachebusting gotchas aren't going to pose a big problem
|
24
|
+
for you
|
25
|
+
|
26
|
+
If you need a more feature rich implementation, Sprockets/AssetPipeline would
|
27
|
+
better fit the bill. This is intended for getting small, sinatra-esque apps
|
28
|
+
up quickly where the gorilla isn't needed yet.
|
29
|
+
|
30
|
+
## Installation
|
31
|
+
|
32
|
+
Add this line to your application's Gemfile:
|
33
|
+
|
34
|
+
gem 'static_signature'
|
35
|
+
|
36
|
+
And then execute:
|
37
|
+
|
38
|
+
$ bundle
|
39
|
+
|
40
|
+
Or install it yourself as:
|
41
|
+
|
42
|
+
$ gem install static_signature
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
Enable the middleware providing the path to your static assets directory:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
use StaticSignature::Middleware, :static_dir => File.join(File.dirname(__FILE__), "public")
|
50
|
+
```
|
51
|
+
|
52
|
+
Note the `static_dir` option is mandatory and must be an absolute path.
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "static_signature/version"
|
2
|
+
require "thread"
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
module StaticSignature
|
6
|
+
class Middleware
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@app = app
|
9
|
+
@static_dir = options.fetch(:static_dir)
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
dup._call(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def _call(env)
|
18
|
+
status, headers, response = @app.call(env)
|
19
|
+
|
20
|
+
if headers["Content-Type"] !~ /text\/html/
|
21
|
+
[status, headers, response]
|
22
|
+
else
|
23
|
+
body = ""
|
24
|
+
response.each do |part|
|
25
|
+
body << part
|
26
|
+
end
|
27
|
+
doc = Nokogiri::HTML(body)
|
28
|
+
bust_tag(doc, "link", "href")
|
29
|
+
bust_tag(doc, "script", "src")
|
30
|
+
body = doc.to_html
|
31
|
+
headers["Content-Length"] = body.bytesize.to_s
|
32
|
+
[status, headers, [body]]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def bust_tag(doc, tag_name, attribute)
|
37
|
+
doc.search(tag_name).each do |node|
|
38
|
+
if node[attribute] && ! node[attribute].include?("//")
|
39
|
+
node[attribute] += "?#{asset_signature(node[attribute])}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def asset_signature(path)
|
45
|
+
full_path = File.join(@static_dir, path)
|
46
|
+
|
47
|
+
@mutex.synchronize {
|
48
|
+
signature_cache[path] ||= Digest::MD5.hexdigest(File.read(full_path))
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def signature_cache
|
53
|
+
Thread.current[:_signature_cache] ||= {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/static_signature/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Brendon Murphy"]
|
6
|
+
gem.email = ["xternal1+github@gmail.com"]
|
7
|
+
gem.description = %q{Rack Middleware for adding query string cache busting signatures on
|
8
|
+
script and style assets}
|
9
|
+
gem.summary = gem.description
|
10
|
+
gem.homepage = ""
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "static_signature"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = StaticSignature::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency("nokogiri")
|
20
|
+
gem.add_development_dependency("rack-test")
|
21
|
+
end
|
data/test/data/js/app.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
alert("test");
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class MiddlewareTest < MiniTest::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
# Read first so we don't exhaust DATA
|
7
|
+
# after a single test
|
8
|
+
TEST_HTML = DATA.read.freeze
|
9
|
+
|
10
|
+
def app
|
11
|
+
Rack::Builder.new do
|
12
|
+
use StaticSignature::Middleware, :static_dir => File.join(File.dirname(__FILE__), "data")
|
13
|
+
run lambda { |env| [200, {'Content-Type' => "text/html"}, [TEST_HTML]]}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def response_doc
|
18
|
+
Nokogiri::HTML(last_response.body)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_link_tag_busting
|
22
|
+
get "/"
|
23
|
+
node = response_doc.search("link").first
|
24
|
+
assert_equal "/css/app.css?b7a03f662ff6716c8a95cad9299de15e", node["href"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_script_tag_busting
|
28
|
+
get "/"
|
29
|
+
node = response_doc.search("script").first
|
30
|
+
assert_equal "/js/app.js?0677e7d10a89eb91d1292cbe35036a15", node["src"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_skip_protocol_busting
|
34
|
+
get "/"
|
35
|
+
assert_match /asset\.js\Z/, response_doc.search("script")[1]["src"]
|
36
|
+
assert_match /vendor\.js\Z/, response_doc.search("script")[2]["src"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_end_tags_intact
|
40
|
+
get "/"
|
41
|
+
assert_match %r{</body>}, last_response.body
|
42
|
+
assert_match %r{</html>}, last_response.body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
__END__
|
47
|
+
|
48
|
+
<!DOCTYPE html>
|
49
|
+
<html>
|
50
|
+
<head>
|
51
|
+
<title>Test HTML</title>
|
52
|
+
<link href="/css/app.css" rel="stylesheet" media="screen">
|
53
|
+
</head
|
54
|
+
<body>
|
55
|
+
<h1>Hello, world!</h1>
|
56
|
+
<script src="/js/app.js"></script>
|
57
|
+
<script src="http://www.example.com/js/asset.js"></script>
|
58
|
+
<script src="//www.example.com/js/vendor.js"></script>
|
59
|
+
</body>
|
60
|
+
</html>
|
61
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: static_signature
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brendon Murphy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: &2151894720 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2151894720
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack-test
|
27
|
+
requirement: &2151894160 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2151894160
|
36
|
+
description: ! 'Rack Middleware for adding query string cache busting signatures on
|
37
|
+
|
38
|
+
script and style assets'
|
39
|
+
email:
|
40
|
+
- xternal1+github@gmail.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- lib/static_signature.rb
|
51
|
+
- lib/static_signature/version.rb
|
52
|
+
- static_signature.gemspec
|
53
|
+
- test/data/css/app.css
|
54
|
+
- test/data/js/app.js
|
55
|
+
- test/middleware_test.rb
|
56
|
+
- test/test_helper.rb
|
57
|
+
homepage: ''
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.8.15
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Rack Middleware for adding query string cache busting signatures on script
|
81
|
+
and style assets
|
82
|
+
test_files:
|
83
|
+
- test/data/css/app.css
|
84
|
+
- test/data/js/app.js
|
85
|
+
- test/middleware_test.rb
|
86
|
+
- test/test_helper.rb
|