async-http-cache 0.1.5 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/async/http/cache/body.rb +25 -14
- data/lib/async/http/cache/general.rb +14 -5
- data/lib/async/http/cache/response.rb +12 -2
- data/lib/async/http/cache/store/memory.rb +11 -2
- data/lib/async/http/cache/version.rb +1 -1
- metadata +9 -59
- data/.github/workflows/development.yml +0 -34
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/Gemfile +0 -4
- data/README.md +0 -68
- data/Rakefile +0 -6
- data/async-http-cache.gemspec +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f2003b72015123e99c75069272771e6247c31abbd55163da86955dd462dc4d6
|
4
|
+
data.tar.gz: dd9798d6736f4e73c06e1a15a6010c192a663042081ee6d92484eddd4b924256
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ba0a34a15023d0e2b3b66052aa233f3180b4706e28040b08c0d8605e288230cee7461ffc8ecc3602575ae57e8b0ca7e713181aa07a83453f201c76680e0a32a
|
7
|
+
data.tar.gz: 8722d2f6aa70df6e830ecd1482916b26bd42d8f18ccd070010fb6c7d5689982b503cceb3df604e14c359d4cd8cdc9b63a0612b0b8bbf7f40b52e7cb029817611
|
@@ -21,38 +21,49 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
require 'protocol/http/body/rewindable'
|
24
|
-
require 'protocol/http/body/
|
24
|
+
require 'protocol/http/body/completable'
|
25
|
+
require 'protocol/http/body/digestable'
|
25
26
|
|
26
27
|
module Async
|
27
28
|
module HTTP
|
28
29
|
module Cache
|
29
30
|
module Body
|
30
|
-
|
31
|
-
|
31
|
+
TRAILER = 'trailer'
|
32
|
+
ETAG = 'etag'
|
33
|
+
|
34
|
+
def self.wrap(response, &block)
|
35
|
+
if body = response.body
|
32
36
|
if body.empty?
|
33
37
|
# A body that is empty? at the outset, is immutable. This generally only applies to HEAD requests.
|
34
|
-
yield
|
38
|
+
yield response, body
|
35
39
|
else
|
36
|
-
#
|
37
|
-
rewindable = ::Protocol::HTTP::Body::Rewindable.
|
40
|
+
# Insert a rewindable body so that we can cache the response body:
|
41
|
+
rewindable = ::Protocol::HTTP::Body::Rewindable.wrap(response)
|
38
42
|
|
39
|
-
|
40
|
-
|
43
|
+
unless response.headers.include?(ETAG)
|
44
|
+
# Compute a digest and add it to the response headers:
|
45
|
+
::Protocol::HTTP::Body::Digestable.wrap(response) do |wrapper|
|
46
|
+
response.headers.add(ETAG, wrapper.etag)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Ensure the etag is listed as a trailer:
|
50
|
+
response.headers.add(TRAILER, ETAG)
|
51
|
+
end
|
41
52
|
|
42
|
-
# Wrap the
|
43
|
-
::Protocol::HTTP::Body::
|
53
|
+
# Wrap the response with the callback:
|
54
|
+
::Protocol::HTTP::Body::Completable.wrap(response) do |error|
|
44
55
|
if error
|
45
|
-
|
56
|
+
Console.logger.error(self) {error}
|
46
57
|
else
|
47
|
-
yield
|
58
|
+
yield response, rewindable.buffered
|
48
59
|
end
|
49
60
|
end
|
50
61
|
end
|
51
62
|
else
|
52
|
-
yield
|
63
|
+
yield response, nil
|
53
64
|
end
|
54
65
|
|
55
|
-
return
|
66
|
+
return response
|
56
67
|
end
|
57
68
|
end
|
58
69
|
end
|
@@ -31,6 +31,7 @@ module Async
|
|
31
31
|
module Cache
|
32
32
|
class General < ::Protocol::HTTP::Middleware
|
33
33
|
CACHE_CONTROL = 'cache-control'
|
34
|
+
|
34
35
|
CONTENT_TYPE = 'content-type'
|
35
36
|
AUTHORIZATION = 'authorization'
|
36
37
|
COOKIE = 'cookie'
|
@@ -91,9 +92,17 @@ module Async
|
|
91
92
|
return response
|
92
93
|
end
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
95
|
+
if request.head? and body = response.body
|
96
|
+
unless body.empty?
|
97
|
+
Console.logger.warn(self) {"HEAD request resulted in non-empty body!"}
|
98
|
+
|
99
|
+
return response
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
return Body.wrap(response) do |response, body|
|
104
|
+
Console.logger.debug(self) {"Updating cache for #{key}..."}
|
105
|
+
@store.insert(key, request, Response.new(response, body))
|
97
106
|
end
|
98
107
|
end
|
99
108
|
|
@@ -104,10 +113,10 @@ module Async
|
|
104
113
|
|
105
114
|
unless cache_control&.no_cache?
|
106
115
|
if response = @store.lookup(key, request)
|
107
|
-
|
116
|
+
Console.logger.debug(self) {"Cache hit for #{key}..."}
|
108
117
|
@count += 1
|
109
118
|
|
110
|
-
#
|
119
|
+
# Return the cached response:
|
111
120
|
return response
|
112
121
|
end
|
113
122
|
end
|
@@ -29,6 +29,9 @@ module Async
|
|
29
29
|
class Response < ::Protocol::HTTP::Response
|
30
30
|
CACHE_CONTROL = 'cache-control'
|
31
31
|
SET_COOKIE = 'set-cookie'
|
32
|
+
ETAG = 'etag'
|
33
|
+
|
34
|
+
X_CACHE = 'x-cache'
|
32
35
|
|
33
36
|
def initialize(response, body)
|
34
37
|
@generated_at = Async::Clock.now
|
@@ -36,16 +39,23 @@ module Async
|
|
36
39
|
super(
|
37
40
|
response.version,
|
38
41
|
response.status,
|
39
|
-
response.headers.
|
42
|
+
response.headers.flatten,
|
40
43
|
body,
|
41
44
|
response.protocol
|
42
45
|
)
|
43
46
|
|
44
47
|
@max_age = @headers[CACHE_CONTROL]&.max_age
|
48
|
+
@etag = nil
|
49
|
+
|
50
|
+
@headers.set(X_CACHE, 'hit')
|
45
51
|
end
|
46
52
|
|
47
53
|
attr :generated_at
|
48
54
|
|
55
|
+
def etag
|
56
|
+
@etag ||= @headers[ETAG]
|
57
|
+
end
|
58
|
+
|
49
59
|
def cachable?
|
50
60
|
if cache_control = @headers[CACHE_CONTROL]
|
51
61
|
if cache_control.private? || !cache_control.public?
|
@@ -57,7 +67,7 @@ module Async
|
|
57
67
|
end
|
58
68
|
|
59
69
|
if set_cookie = @headers[SET_COOKIE]
|
60
|
-
|
70
|
+
Console.logger.warn(self) {"Cannot cache response with set-cookie header!"}
|
61
71
|
return false
|
62
72
|
end
|
63
73
|
|
@@ -33,14 +33,14 @@ module Async
|
|
33
33
|
@miss = 0
|
34
34
|
@pruned = 0
|
35
35
|
|
36
|
-
@gardener = Async do |task|
|
36
|
+
@gardener = Async(transient: true, annotation: self.class) do |task|
|
37
37
|
while true
|
38
38
|
task.sleep(60)
|
39
39
|
|
40
40
|
pruned = self.prune
|
41
41
|
@pruned += pruned
|
42
42
|
|
43
|
-
|
43
|
+
Console.logger.debug(self) do |buffer|
|
44
44
|
if pruned > 0
|
45
45
|
buffer.puts "Pruned #{pruned} entries."
|
46
46
|
end
|
@@ -64,6 +64,9 @@ module Async
|
|
64
64
|
|
65
65
|
attr :index
|
66
66
|
|
67
|
+
IF_NONE_MATCH = 'if-none-match'
|
68
|
+
NOT_MODIFIED = ::Protocol::HTTP::Response[304]
|
69
|
+
|
67
70
|
def lookup(key, request)
|
68
71
|
if response = @index[key]
|
69
72
|
if response.expired?
|
@@ -74,6 +77,12 @@ module Async
|
|
74
77
|
return nil
|
75
78
|
end
|
76
79
|
|
80
|
+
if etags = request.headers[IF_NONE_MATCH]
|
81
|
+
if etags.include?(response.etag)
|
82
|
+
return NOT_MODIFIED
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
77
86
|
@hit += 1
|
78
87
|
|
79
88
|
return response.dup
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-http-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-http
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: protocol-http
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
19
|
+
version: '0.56'
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
26
|
+
version: '0.56'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: async-rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,20 +52,6 @@ dependencies:
|
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: bundler
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
56
|
name: rspec
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,34 +66,12 @@ dependencies:
|
|
94
66
|
- - ">="
|
95
67
|
- !ruby/object:Gem::Version
|
96
68
|
version: '0'
|
97
|
-
|
98
|
-
name: rake
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
description:
|
69
|
+
description:
|
112
70
|
email:
|
113
|
-
- samuel.williams@oriontransfer.co.nz
|
114
71
|
executables: []
|
115
72
|
extensions: []
|
116
73
|
extra_rdoc_files: []
|
117
74
|
files:
|
118
|
-
- ".github/workflows/development.yml"
|
119
|
-
- ".gitignore"
|
120
|
-
- ".rspec"
|
121
|
-
- Gemfile
|
122
|
-
- README.md
|
123
|
-
- Rakefile
|
124
|
-
- async-http-cache.gemspec
|
125
75
|
- lib/async/http/cache.rb
|
126
76
|
- lib/async/http/cache/body.rb
|
127
77
|
- lib/async/http/cache/general.rb
|
@@ -134,7 +84,7 @@ homepage: https://github.com/socketry/async-http-cache
|
|
134
84
|
licenses:
|
135
85
|
- MIT
|
136
86
|
metadata: {}
|
137
|
-
post_install_message:
|
87
|
+
post_install_message:
|
138
88
|
rdoc_options: []
|
139
89
|
require_paths:
|
140
90
|
- lib
|
@@ -149,8 +99,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
99
|
- !ruby/object:Gem::Version
|
150
100
|
version: '0'
|
151
101
|
requirements: []
|
152
|
-
rubygems_version: 3.
|
153
|
-
signing_key:
|
102
|
+
rubygems_version: 3.3.0.dev
|
103
|
+
signing_key:
|
154
104
|
specification_version: 4
|
155
105
|
summary: Standard-compliant cache for async-http.
|
156
106
|
test_files: []
|
@@ -1,34 +0,0 @@
|
|
1
|
-
name: Development
|
2
|
-
|
3
|
-
on: [push, pull_request]
|
4
|
-
|
5
|
-
jobs:
|
6
|
-
test:
|
7
|
-
strategy:
|
8
|
-
matrix:
|
9
|
-
os:
|
10
|
-
- ubuntu
|
11
|
-
- macos
|
12
|
-
|
13
|
-
ruby:
|
14
|
-
- 2.4
|
15
|
-
- 2.5
|
16
|
-
- 2.6
|
17
|
-
- 2.7
|
18
|
-
|
19
|
-
include:
|
20
|
-
- os: 'ubuntu'
|
21
|
-
ruby: '2.6'
|
22
|
-
env: COVERAGE=PartialSummary,Coveralls
|
23
|
-
|
24
|
-
runs-on: ${{matrix.os}}-latest
|
25
|
-
|
26
|
-
steps:
|
27
|
-
- uses: actions/checkout@v1
|
28
|
-
- uses: ruby/setup-ruby@v1
|
29
|
-
with:
|
30
|
-
ruby-version: ${{matrix.ruby}}
|
31
|
-
- name: Install dependencies
|
32
|
-
run: bundle install
|
33
|
-
- name: Run tests
|
34
|
-
run: ${{matrix.env}} bundle exec rspec
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/Gemfile
DELETED
data/README.md
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# Async::HTTP::Cache
|
2
|
-
|
3
|
-
Provides a cache middleware for `Async::HTTP` clients and servers.
|
4
|
-
|
5
|
-
[![Development](https://github.com/socketry/async-http-cache/workflows/Development/badge.svg?branch=master)](https://github.com/socketry/async-http-cache/actions?workflow=Development)
|
6
|
-
|
7
|
-
## Usage
|
8
|
-
|
9
|
-
### Client Side
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
require 'async'
|
13
|
-
require 'async/http'
|
14
|
-
require 'async/http/cache'
|
15
|
-
|
16
|
-
endpoint = Async::HTTP::Endpoint.parse("https://www.oriontransfer.co.nz")
|
17
|
-
client = Async::HTTP::Client.new(endpoint)
|
18
|
-
cache = Async::HTTP::Cache::General.new(client)
|
19
|
-
|
20
|
-
Async do
|
21
|
-
2.times do
|
22
|
-
response = cache.get("/products/index")
|
23
|
-
puts response.inspect
|
24
|
-
# <Async::HTTP::Protocol::HTTP2::Response ...>
|
25
|
-
# <Async::HTTP::Cache::Response ...>
|
26
|
-
response.finish
|
27
|
-
end
|
28
|
-
ensure
|
29
|
-
cache.close
|
30
|
-
end
|
31
|
-
```
|
32
|
-
|
33
|
-
## Vary
|
34
|
-
|
35
|
-
The `vary` header creates a headache for proxy implementations, because it creates a combinatorial explosion of cache keys, even if the content is the same. Try to avoid it unless absolutely necessary.
|
36
|
-
|
37
|
-
## Contributing
|
38
|
-
|
39
|
-
1. Fork it
|
40
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
41
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
42
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
43
|
-
5. Create new Pull Request
|
44
|
-
|
45
|
-
|
46
|
-
## License
|
47
|
-
|
48
|
-
Released under the MIT license.
|
49
|
-
|
50
|
-
Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
51
|
-
|
52
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
53
|
-
of this software and associated documentation files (the "Software"), to deal
|
54
|
-
in the Software without restriction, including without limitation the rights
|
55
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
56
|
-
copies of the Software, and to permit persons to whom the Software is
|
57
|
-
furnished to do so, subject to the following conditions:
|
58
|
-
|
59
|
-
The above copyright notice and this permission notice shall be included in
|
60
|
-
all copies or substantial portions of the Software.
|
61
|
-
|
62
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
63
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
64
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
65
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
66
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
67
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
68
|
-
THE SOFTWARE.
|
data/Rakefile
DELETED
data/async-http-cache.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
|
2
|
-
require_relative 'lib/async/http/cache/version'
|
3
|
-
|
4
|
-
Gem::Specification.new do |spec|
|
5
|
-
spec.name = "async-http-cache"
|
6
|
-
spec.version = Async::HTTP::Cache::VERSION
|
7
|
-
spec.authors = ["Samuel Williams"]
|
8
|
-
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
-
|
10
|
-
spec.summary = "Standard-compliant cache for async-http."
|
11
|
-
spec.homepage = "https://github.com/socketry/async-http-cache"
|
12
|
-
spec.license = "MIT"
|
13
|
-
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
15
|
-
|
16
|
-
# Specify which files should be added to the gem when it is released.
|
17
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
-
end
|
21
|
-
|
22
|
-
spec.require_paths = ["lib"]
|
23
|
-
|
24
|
-
spec.add_dependency "async-http"
|
25
|
-
spec.add_dependency "protocol-http", "~> 0.14"
|
26
|
-
|
27
|
-
spec.add_development_dependency "async-rspec", "~> 1.10"
|
28
|
-
|
29
|
-
spec.add_development_dependency "covered"
|
30
|
-
spec.add_development_dependency "bundler"
|
31
|
-
spec.add_development_dependency "rspec"
|
32
|
-
spec.add_development_dependency "rake"
|
33
|
-
end
|