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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc8e38cbf05084350cc6d91e0f6f35cd612f14d3554b720c48ebb03abf3331ae
4
- data.tar.gz: 05b44c8cbc06f3ff26b9f9b7d2fa8643278d7067adb902d8ce4b11726f23f6a6
3
+ metadata.gz: 4f2003b72015123e99c75069272771e6247c31abbd55163da86955dd462dc4d6
4
+ data.tar.gz: dd9798d6736f4e73c06e1a15a6010c192a663042081ee6d92484eddd4b924256
5
5
  SHA512:
6
- metadata.gz: cd3729f3a2877372c7df040dd1798d6ef55e44688dbd9d8ae4d40e9ee4a0d4eba37d1cc53b0c6d512d3c1277bd9dc043b423c5a21b54d4085da827cf1481c753
7
- data.tar.gz: e3a1951d11b75c3da93f585bcb8cb4254e93d16d73282a9b1f713d4208c28de2bbb991b3305516db7cb47c2b6cd0df343d5116cec962792a73d94e9f8fe09f87
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/streamable'
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
- def self.wrap(message, &block)
31
- if body = message.body
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 message, body
38
+ yield response, body
35
39
  else
36
- # Create a rewindable body wrapping the message body:
37
- rewindable = ::Protocol::HTTP::Body::Rewindable.new(body)
40
+ # Insert a rewindable body so that we can cache the response body:
41
+ rewindable = ::Protocol::HTTP::Body::Rewindable.wrap(response)
38
42
 
39
- # Set the message body to the rewindable body:
40
- message.body = rewindable
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 message with the callback:
43
- ::Protocol::HTTP::Body::Streamable.wrap(message) do |error|
53
+ # Wrap the response with the callback:
54
+ ::Protocol::HTTP::Body::Completable.wrap(response) do |error|
44
55
  if error
45
- Async.logger.error(self) {error}
56
+ Console.logger.error(self) {error}
46
57
  else
47
- yield message, rewindable.buffered
58
+ yield response, rewindable.buffered
48
59
  end
49
60
  end
50
61
  end
51
62
  else
52
- yield message, nil
63
+ yield response, nil
53
64
  end
54
65
 
55
- return message
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
- return Body.wrap(response) do |message, body|
95
- Async.logger.debug(self) {"Updating cache for #{key}..."}
96
- @store.insert(key, request, Response.new(message, body))
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
- Async.logger.debug(self) {"Cache hit for #{key}..."}
116
+ Console.logger.debug(self) {"Cache hit for #{key}..."}
108
117
  @count += 1
109
118
 
110
- # Create a dup of the response:
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.dup,
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
- Async.logger.warn(self) {"Cannot cache response with set-cookie header!"}
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
- Async.logger.debug(self) do |buffer|
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
@@ -23,7 +23,7 @@
23
23
  module Async
24
24
  module HTTP
25
25
  module Cache
26
- VERSION = "0.1.5"
26
+ VERSION = "0.4.2"
27
27
  end
28
28
  end
29
29
  end
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.1.5
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: 2020-03-24 00:00:00.000000000 Z
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.14'
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.14'
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
- - !ruby/object:Gem::Dependency
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.1.2
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
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
- Gemfile.lock
13
- .covered.db
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --warnings
3
- --require spec_helper
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in async-http-cache.gemspec
4
- gemspec
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
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -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