rack-steady_etag 0.1.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -4
- data/Gemfile.lock +6 -8
- data/README.md +18 -4
- data/lib/rack/steady_etag/version.rb +1 -1
- data/lib/rack/steady_etag.rb +53 -31
- data/rack-steady_etag.gemspec +5 -5
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8caa8d352b72433a6659f50b6c9216cf03b265b19016134fa259719d24c46b4e
|
4
|
+
data.tar.gz: 74f01fc86755933b87361615bd6cb62568e44bfa404565261c0c180dcd5b399c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cbd8de00297924f758d0c32151d17991fe8e235d943969544cdad632f50cca43eba12ef133c1a7690d91def4d8f997ab48535417457f62c8cb9d36a07a7e9d9
|
7
|
+
data.tar.gz: c2df595d03a3e3f46b2be21f4da14f76460556a0b8e6628959d7156d1a5285c79eb9c1a45415ad343904ed388c4645320ddc94a2422184727798d4b432eecd2e
|
data/CHANGELOG.md
CHANGED
@@ -5,13 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
|
|
5
5
|
|
6
6
|
## Unreleased
|
7
7
|
|
8
|
-
|
8
|
+
## 0.2.2 - 2022-05-12
|
9
9
|
|
10
|
-
|
10
|
+
- Don't raise an error when processing binary content.
|
11
11
|
|
12
|
-
## 0.
|
12
|
+
## 0.2.1 - 2022-05-12
|
13
|
+
|
14
|
+
- Only strip patterns for HTML and XHTML responses.
|
15
|
+
|
16
|
+
## 0.2.0 - 2022-05-12
|
13
17
|
|
14
|
-
|
18
|
+
- Be more compatible with Rack 2.2.3:
|
19
|
+
- Always set a `Cache-Control` header, even for responses that we don't try to digest.
|
20
|
+
- Strip patterns for responses with `Cache-Control: public`
|
21
|
+
- Requires Rack 2.x (we want to break with Rack 3)
|
22
|
+
|
23
|
+
## 0.1.1 - 2022-05-16
|
15
24
|
|
16
25
|
- Activate rubygems MFA
|
17
26
|
|
data/Gemfile.lock
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rack-steady_etag (0.
|
4
|
+
rack-steady_etag (0.2.2)
|
5
5
|
activesupport (>= 3.2)
|
6
|
-
rack
|
6
|
+
rack (~> 2.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activesupport (
|
11
|
+
activesupport (7.0.1)
|
12
12
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
13
|
i18n (>= 1.6, < 2)
|
14
14
|
minitest (>= 5.1)
|
15
15
|
tzinfo (~> 2.0)
|
16
|
-
zeitwerk (~> 2.3)
|
17
16
|
byebug (11.1.3)
|
18
|
-
concurrent-ruby (1.1.
|
17
|
+
concurrent-ruby (1.1.10)
|
19
18
|
diff-lcs (1.4.4)
|
20
|
-
i18n (1.
|
19
|
+
i18n (1.10.0)
|
21
20
|
concurrent-ruby (~> 1.0)
|
22
|
-
minitest (5.
|
21
|
+
minitest (5.15.0)
|
23
22
|
rack (2.2.3)
|
24
23
|
rake (13.0.6)
|
25
24
|
rspec (3.10.0)
|
@@ -37,7 +36,6 @@ GEM
|
|
37
36
|
rspec-support (3.10.3)
|
38
37
|
tzinfo (2.0.4)
|
39
38
|
concurrent-ruby (~> 1.0)
|
40
|
-
zeitwerk (2.5.1)
|
41
39
|
|
42
40
|
PLATFORMS
|
43
41
|
x86_64-linux
|
data/README.md
CHANGED
@@ -20,17 +20,29 @@ By default Rails uses [`Rack::ETag`](https://rdoc.info/github/rack/rack/Rack/ETa
|
|
20
20
|
You can add your own patterns:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
Rack::SteadyETag::
|
23
|
+
Rack::SteadyETag::STRIP_PATTERNS << /<meta name="XSRF-TOKEN" value="[^"]+">/
|
24
24
|
```
|
25
25
|
|
26
26
|
You can also push lambda for arbitrary transformations:
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
Rack::SteadyETag::
|
29
|
+
Rack::SteadyETag::STRIP_PATTERNS << -> { |text| text.gsub(/<meta name="XSRF-TOKEN" value="[^"]+">/, '') }
|
30
30
|
```
|
31
31
|
|
32
32
|
Transformations are only applied for the `ETag` hash. The response body will not be changed.
|
33
33
|
|
34
|
+
## What responses are processed
|
35
|
+
|
36
|
+
This middleware will process responses that match all of the following:
|
37
|
+
|
38
|
+
- Responses with a HTTP status of 200 or 201.
|
39
|
+
- Responses with a `Content-Type` of `text/html` or `application/xhtml+xml`.
|
40
|
+
- Responses with a body.
|
41
|
+
|
42
|
+
Responses should also have an UTF-8 encoding (not checked by the middleware).
|
43
|
+
|
44
|
+
This middleware can also add a default `Cache-Control` header for responses it *didn't* process. This is passed as an argument during middleware initialization (see *Installation* below).
|
45
|
+
|
34
46
|
## Covered edge cases
|
35
47
|
|
36
48
|
- Different `ETags` are generated when the same content is accessed with different Rack sessions.
|
@@ -39,7 +51,7 @@ Transformations are only applied for the `ETag` hash. The response body will not
|
|
39
51
|
- No `ETag` is generated when the response already has an `Last-Modified` header.
|
40
52
|
|
41
53
|
|
42
|
-
## Installation
|
54
|
+
## Installation in Rails
|
43
55
|
|
44
56
|
Add this line to your application's Gemfile:
|
45
57
|
|
@@ -56,9 +68,11 @@ bundle install
|
|
56
68
|
In your `config/application.rb`:
|
57
69
|
|
58
70
|
```ruby
|
59
|
-
config.middleware.swap Rack::ETag, Rack::SteadyETag
|
71
|
+
config.middleware.swap Rack::ETag, Rack::SteadyETag, 'no-cache'
|
60
72
|
```
|
61
73
|
|
74
|
+
The `'no-cache'` argument is the default `Cache-Control` for responses that cannot be digested. While it may feel surprising that the middleware changes the `Cache-Control` header in such a case, the [Rails default middleware stack](https://github.com/rails/rails/blob/d96609505511a76c618dc3adfa3ca4679317d008/railties/lib/rails/application/default_middleware_stack.rb#L81) configures the same behavior.
|
75
|
+
|
62
76
|
|
63
77
|
## Development
|
64
78
|
|
data/lib/rack/steady_etag.rb
CHANGED
@@ -6,8 +6,8 @@ require_relative "steady_etag/version"
|
|
6
6
|
|
7
7
|
module Rack
|
8
8
|
|
9
|
-
# Based on Rack::Etag
|
10
|
-
# https://github.com/rack/rack/blob/
|
9
|
+
# Based on Rack::Etag from rack 2.2.3
|
10
|
+
# https://github.com/rack/rack/blob/v2.2.3/lib/rack/etag.rb
|
11
11
|
#
|
12
12
|
# Automatically sets the ETag header on all String bodies.
|
13
13
|
#
|
@@ -15,20 +15,30 @@ module Rack
|
|
15
15
|
# a sendfile body (body.responds_to :to_path) is given (since such cases
|
16
16
|
# should be handled by apache/nginx).
|
17
17
|
class SteadyETag
|
18
|
+
|
19
|
+
# Yes, Rack::ETag sets a default Cache-Control for responses that it can digest.
|
18
20
|
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
19
21
|
|
20
|
-
|
22
|
+
STRIP_PATTERNS = [
|
21
23
|
/<meta\b[^>]*\bname=(["'])csrf-token\1[^>]+>/i,
|
22
24
|
/<meta\b[^>]*\bname=(["'])csp-nonce\1[^>]+>/i,
|
23
25
|
/<input\b[^>]*\bname=(["'])authenticity_token\1[^>]+>/i,
|
24
26
|
lambda { |string| string.gsub(/(<script\b[^>]*)\bnonce=(["'])[^"']+\2+/i, '\1') }
|
25
27
|
]
|
26
28
|
|
27
|
-
|
29
|
+
STRIP_CONTENT_TYPES = %w[
|
30
|
+
text/html
|
31
|
+
application/xhtml+xml
|
32
|
+
]
|
33
|
+
|
34
|
+
def initialize(app, no_digest_cache_control = nil, digest_cache_control = DEFAULT_CACHE_CONTROL)
|
28
35
|
@app = app
|
36
|
+
|
29
37
|
@digest_cache_control = digest_cache_control
|
38
|
+
|
39
|
+
# Rails sets a default `Cache-Control: no-cache` for responses that we cannot digest.
|
40
|
+
# See https://github.com/rails/rails/blob/d96609505511a76c618dc3adfa3ca4679317d008/railties/lib/rails/application/default_middleware_stack.rb#L81
|
30
41
|
@no_digest_cache_control = no_digest_cache_control
|
31
|
-
@ignore_patterns = ignore_patterns
|
32
42
|
end
|
33
43
|
|
34
44
|
def call(env)
|
@@ -45,6 +55,18 @@ module Rack
|
|
45
55
|
headers[ETAG] = %(W/"#{digest}") if digest
|
46
56
|
end
|
47
57
|
|
58
|
+
# It would make more sense to only set a Cache-Control for responses that we process.
|
59
|
+
# However, the original Rack::ETag sets Cache-Control: @no_digest_cache_control
|
60
|
+
# for all responses, even responses that we don't otherwise modify.
|
61
|
+
# Hence if we move this code into the `if` above we would remove Rails' default
|
62
|
+
# Cache-Control headers for non-digestable responses, which would be a considerable
|
63
|
+
# change in behavior.
|
64
|
+
if digest
|
65
|
+
set_cache_control_with_digest(headers)
|
66
|
+
else
|
67
|
+
set_cache_control_without_digest(headers)
|
68
|
+
end
|
69
|
+
|
48
70
|
[status, headers, body]
|
49
71
|
end
|
50
72
|
|
@@ -63,6 +85,9 @@ module Rack
|
|
63
85
|
end
|
64
86
|
|
65
87
|
def etag_body?(body)
|
88
|
+
# Rack main branch checks for `:to_ary` here to exclude streaming responses,
|
89
|
+
# but that had other issues for me in testing. Maybe recheck when there is a
|
90
|
+
# new Rack release after 2.2.3.
|
66
91
|
!body.respond_to?(:to_path)
|
67
92
|
end
|
68
93
|
|
@@ -70,51 +95,48 @@ module Rack
|
|
70
95
|
headers.key?(ETAG) || headers.key?('Last-Modified')
|
71
96
|
end
|
72
97
|
|
73
|
-
def cache_control_private?(headers)
|
74
|
-
headers[CACHE_CONTROL] && headers[CACHE_CONTROL] =~ /\bprivate\b/
|
75
|
-
end
|
76
|
-
|
77
98
|
def digest_body(body, headers, session)
|
78
99
|
parts = []
|
79
100
|
digest = nil
|
80
101
|
|
102
|
+
strippable_response = STRIP_CONTENT_TYPES.include?(headers['Content-Type'])
|
103
|
+
|
81
104
|
body.each do |part|
|
82
105
|
parts << part
|
83
106
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
unless digest
|
92
|
-
digest = Digest::SHA256.new
|
93
|
-
|
94
|
-
if session && (session_id = session['session_id'])
|
95
|
-
digest << session_id.to_s
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
107
|
+
# Note that `part` can be a string with binary data here.
|
108
|
+
# It's important to check emptiness with #empty? instead of #blank?, since #blank?
|
109
|
+
# internally calls String#match? and that explodes if the string is not valid UTF-8.
|
110
|
+
unless part.empty?
|
111
|
+
digest ||= initialize_digest(session)
|
112
|
+
part = strip_patterns(part) if strippable_response
|
99
113
|
digest << part
|
100
114
|
end
|
101
115
|
end
|
102
116
|
|
103
117
|
if digest
|
104
118
|
digest = digest.hexdigest.byteslice(0,32)
|
105
|
-
else
|
106
|
-
set_cache_control_without_digest(headers)
|
107
119
|
end
|
108
120
|
|
109
121
|
[digest, parts]
|
110
122
|
end
|
111
123
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
124
|
+
def initialize_digest(session)
|
125
|
+
digest = Digest::SHA256.new
|
126
|
+
|
127
|
+
if session && (session_id = session['session_id'])
|
128
|
+
digest << session_id.to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
digest
|
132
|
+
end
|
133
|
+
|
134
|
+
def strip_patterns(html)
|
135
|
+
STRIP_PATTERNS.each do |pattern|
|
136
|
+
if pattern.respond_to?(:call)
|
137
|
+
html = pattern.call(html)
|
116
138
|
else
|
117
|
-
html = html.gsub(
|
139
|
+
html = html.gsub(pattern, '')
|
118
140
|
end
|
119
141
|
end
|
120
142
|
html
|
data/rack-steady_etag.gemspec
CHANGED
@@ -31,10 +31,10 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
34
|
+
# I expect Rack 3 to have a new ETag middleware to no longer buffer
|
35
|
+
# streaming responses: https://github.com/rack/rack/issues/1619
|
36
|
+
# Once Rack 3 is out we should release a new version of this gem.
|
37
|
+
spec.add_dependency "rack", '~>2.0'
|
37
38
|
|
38
|
-
|
39
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
spec.add_dependency 'activesupport', '>= 3.2'
|
40
40
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-steady_etag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '2.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0'
|
85
85
|
requirements: []
|
86
|
-
rubygems_version: 3.
|
86
|
+
rubygems_version: 3.2.6
|
87
87
|
signing_key:
|
88
88
|
specification_version: 4
|
89
89
|
summary: Rack Middleware that produces the same ETag for responses that only differ
|