async-http-cache 0.1.1 → 0.2.0

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: 923d97088cda9896dcec9c48c21b6142fc9002acfc15ae70a5cb6fac4eacf086
4
- data.tar.gz: 9819dd06fa5f44a42c7b9f866477fa163df6e857ce0c5618306f5962f2bbfcc1
3
+ metadata.gz: c0819bedaf18e394d19ae5ac3e53fa5eb418f2166252b4a578c6087a3a5b4704
4
+ data.tar.gz: b9d593ee6e8ecef570603c486b04fd52aaaec9c227abe73118cfa0463a54c58d
5
5
  SHA512:
6
- metadata.gz: c1ffbd84fe0928014e500f0290a25720581e50ef688e30ad8161fd3b62ea7a6acc21fe2b40a12cf47ef0f3ec2e007e2613f63cb4fdf1518bf324ae88c63b58ad
7
- data.tar.gz: e4ece204ed54c4d23c1e831d887ae2b7cb169e702b80e890f4bac5438f393fb31c15a873f494ac1ffcc1d3a88663b4eb12e0abb2c95c9fee4e4f030771158b8c
6
+ metadata.gz: fd1bf4dc1595e38f251094969895398b4b335380bb654cd7449197932f657f0aaa3d0f849d8f58846b1ee23ff317f5a6c14359c458e5c25c34630f521af4a620
7
+ data.tar.gz: dbdc2267bb3a61ce7e376bb4f76c37c81913b41a48b98eb220856b1b2352aac3e00a63191125404318c0880a8e83d7ef10a49eb8bdfc85ae87f2c2e047f864ed
@@ -11,7 +11,6 @@ jobs:
11
11
  - macos
12
12
 
13
13
  ruby:
14
- - 2.4
15
14
  - 2.5
16
15
  - 2.6
17
16
  - 2.7
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in async-http-cache.gemspec
4
4
  gemspec
5
+
6
+ # gem "async-http", path: "../async-http"
7
+ # gem "protocol-http", path: "../protocol-http"
8
+ # gem "protocol-http1", path: "../protocol-http1"
@@ -21,13 +21,12 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_dependency "async-http"
25
- spec.add_dependency "protocol-http", "~> 0.14.4"
24
+ spec.add_dependency "async-http", "~> 0.51"
26
25
 
27
26
  spec.add_development_dependency "async-rspec", "~> 1.10"
28
27
 
29
28
  spec.add_development_dependency "covered"
30
29
  spec.add_development_dependency "bundler"
31
30
  spec.add_development_dependency "rspec"
32
- spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "bake-bundler"
33
32
  end
@@ -22,32 +22,48 @@
22
22
 
23
23
  require 'protocol/http/body/rewindable'
24
24
  require 'protocol/http/body/streamable'
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
32
- # Create a rewindable body wrapping the message body:
33
- rewindable = ::Protocol::HTTP::Body::Rewindable.new(body)
34
-
35
- # Set the message body to the rewindable body:
36
- message.body = rewindable
37
-
38
- # Wrap the message with the callback:
39
- ::Protocol::HTTP::Body::Streamable.wrap(message) do |error|
40
- if error
41
- Async.logger.error(self) {error}
42
- else
43
- yield message, rewindable.buffered
31
+ TRAILERS = 'trailers'
32
+ ETAG = 'etag'
33
+
34
+ def self.wrap(response, &block)
35
+ if body = response.body
36
+ if body.empty?
37
+ # A body that is empty? at the outset, is immutable. This generally only applies to HEAD requests.
38
+ yield response, body
39
+ else
40
+ # Insert a rewindable body so that we can cache the response body:
41
+ rewindable = ::Protocol::HTTP::Body::Rewindable.wrap(response)
42
+
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(TRAILERS, ETAG)
51
+ end
52
+
53
+ # Wrap the response with the callback:
54
+ ::Protocol::HTTP::Body::Streamable.wrap(response) do |error|
55
+ if error
56
+ Async.logger.error(self) {error}
57
+ else
58
+ yield response, rewindable.buffered
59
+ end
44
60
  end
45
61
  end
46
62
  else
47
- yield message, nil
63
+ yield response, nil
48
64
  end
49
65
 
50
- return message
66
+ return response
51
67
  end
52
68
  end
53
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
+ if request.head? and body = response.body
96
+ unless body.empty?
97
+ Async.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|
95
104
  Async.logger.debug(self) {"Updating cache for #{key}..."}
96
- @store.insert(key, request, Response.new(message, body))
105
+ @store.insert(key, request, Response.new(response, body))
97
106
  end
98
107
  end
99
108
 
@@ -107,7 +116,7 @@ module Async
107
116
  Async.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,21 +39,31 @@ 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?
52
62
  return false
53
63
  end
64
+ else
65
+ # No cache control header...
66
+ return false
54
67
  end
55
68
 
56
69
  if set_cookie = @headers[SET_COOKIE]
@@ -66,7 +79,9 @@ module Async
66
79
  end
67
80
 
68
81
  def expired?
69
- self.age > @max_age
82
+ if @max_age
83
+ self.age > @max_age
84
+ end
70
85
  end
71
86
 
72
87
  def dup
@@ -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
@@ -86,7 +95,8 @@ module Async
86
95
 
87
96
  def insert(key, request, response)
88
97
  if @index.size < @limit
89
- if response.body.length < 1024*64
98
+ length = response.body&.length
99
+ if length.nil? or length < 1024*64
90
100
  @index[key] = response
91
101
  end
92
102
  end
@@ -23,7 +23,7 @@
23
23
  module Async
24
24
  module HTTP
25
25
  module Cache
26
- VERSION = "0.1.1"
26
+ VERSION = "0.2.0"
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.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-12 00:00:00.000000000 Z
11
+ date: 2020-04-08 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.4
19
+ version: '0.51'
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.4
26
+ version: '0.51'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: async-rspec
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -95,7 +81,7 @@ dependencies:
95
81
  - !ruby/object:Gem::Version
96
82
  version: '0'
97
83
  - !ruby/object:Gem::Dependency
98
- name: rake
84
+ name: bake-bundler
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - ">="
@@ -120,7 +106,6 @@ files:
120
106
  - ".rspec"
121
107
  - Gemfile
122
108
  - README.md
123
- - Rakefile
124
109
  - async-http-cache.gemspec
125
110
  - lib/async/http/cache.rb
126
111
  - lib/async/http/cache/body.rb
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