shaf_client 0.4.0 → 0.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7299a5c0ec8bb7cb0922bb5cddee002c4007e40181bf3ded4a8d28c503185858
4
- data.tar.gz: 576b70f8568906c69127f94ca123c9e154cd32cd0ac08efd99a14213f42aa7c6
3
+ metadata.gz: 7c7758e0a388759de54cd305d5b1541c01c071d0155939fca0dd8a2f9fb57438
4
+ data.tar.gz: c297702586a42855ec25e3f8e0d9fef33936393b3ade5b3258a2a89b03b85896
5
5
  SHA512:
6
- metadata.gz: 598ebc50a8981d76c9bc32bf878e7900d4a8d3d5c3a8b5c8eb7d75a8e1c7754f17d7b2a3104964fd3ba34502d9eb8d56171b23c76f87de9805f9bbef48826edb
7
- data.tar.gz: 536c50642866b57a5697d2e95f1fab76981af719e69c54538415aeb739f166562a3d67d10415f03e28b64ee9b2f10f18cf7bf7f464123c79e3704461aa932e23
6
+ metadata.gz: 2a82f835d9fdd3ec4a9fd131c93263fcfc99f51ac737cafb3e0fdec5d75b51b4f3280d0b0110d75f1d80d097462437bbd2a72aa754eb46a02db8459861d6a12a
7
+ data.tar.gz: bb44a183ec62744bfdc163e526d072ba00b543eea33098a3d2828ad58fc16aa9b0fc444a129d3b5e420707530951c44be51f88c63acacf8d4ecab114cdd645ed
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- =ft0���ohp����{tk��y���ȹ����A��4rS%�vH^f��.�akg~�,����m����T��y9g<'��yN�-D�025q!x�������� �1���z1��j7 :]�r���Z���'���M�&X�C��L�C�]���{�˭~���~F��Oe3��
2
- d��b�%���:�[Q<=w�/CF1o]Wh
1
+ 2�ӳ�&;M����ťPY�����"��$y��J��<uVZY���H>����>��%�\w�����]�� �'huQ��Ok��8JlӦ�f��&����'��[�)d�!�b�������f$��H�&rz^�;m,��
2
+ �w ��Y��+3)G?�����jO0:��19}�,ꍤj��N<ⷈ�-ڒb�f���0�[U�?y��:,�������JG)�/>���5J着L�2㤛S�R�6�ƥ�1�
data/lib/shaf_client.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'faraday'
4
+ require 'faraday-http-cache'
4
5
  require 'json'
5
- require 'shaf_client/middleware/http_cache'
6
6
  require 'shaf_client/middleware/redirect'
7
7
  require 'shaf_client/resource'
8
8
  require 'shaf_client/form'
@@ -75,20 +75,20 @@ class ShafClient
75
75
 
76
76
  def setup_client
77
77
  @adapter = options.fetch(:faraday_adapter, DEFAULT_ADAPTER)
78
+ cache_params = faraday_cache_params(options)
78
79
 
79
80
  @client = Faraday.new(url: @root_uri) do |conn|
80
81
  conn.basic_auth(@user, @pass) if basic_auth?
81
- conn.use Middleware::HttpCache, cache_options
82
82
  conn.use Middleware::Redirect
83
+ conn.use Faraday::HttpCache, **cache_params
83
84
  connect_adapter(conn)
84
85
  end
85
86
  end
86
87
 
87
- def cache_options
88
- options.merge(
89
- accessed_by: self,
90
- auth_header: auth_header
91
- )
88
+ def faraday_cache_params(options)
89
+ options.fetch(:faraday_http_cache, shared_cache: false).tap do |cache_params|
90
+ cache_params[:store] ||= options[:http_cache_store] if options[:http_cache_store]
91
+ end
92
92
  end
93
93
 
94
94
  def connect_adapter(connection)
@@ -100,7 +100,6 @@ class ShafClient
100
100
  def request(method:, uri:, payload: nil, opts: {})
101
101
  payload = JSON.generate(payload) if payload&.is_a?(Hash)
102
102
  headers = default_headers(method).merge(opts.fetch(:headers, {}))
103
- headers[:skip_cache] = true if opts[:skip_cache]
104
103
 
105
104
  @client.send(method) do |req|
106
105
  req.url uri
@@ -59,7 +59,7 @@ class ShafClient
59
59
  end
60
60
 
61
61
  def reload!
62
- self << get(:self, skip_cache: true)
62
+ self << get(:self, headers: {'Cache-Control': 'no-cache'})
63
63
  end
64
64
 
65
65
  protected
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shaf_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sammy Henningsson
@@ -30,7 +30,7 @@ cert_chain:
30
30
  ZMhjYR7sRczGJx+GxGU2EaR0bjRsPVlC4ywtFxoOfRG3WaJcpWGEoAoMJX6Z0bRv
31
31
  M40=
32
32
  -----END CERTIFICATE-----
33
- date: 2019-08-15 00:00:00.000000000 Z
33
+ date: 2019-08-23 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: faraday
@@ -46,6 +46,20 @@ dependencies:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
48
  version: '0.15'
49
+ - !ruby/object:Gem::Dependency
50
+ name: faraday-http-cache
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '2.0'
49
63
  - !ruby/object:Gem::Dependency
50
64
  name: rake
51
65
  requirement: !ruby/object:Gem::Requirement
@@ -91,14 +105,6 @@ files:
91
105
  - lib/shaf_client/curie.rb
92
106
  - lib/shaf_client/form.rb
93
107
  - lib/shaf_client/link.rb
94
- - lib/shaf_client/middleware/http_cache.rb
95
- - lib/shaf_client/middleware/http_cache/accessor.rb
96
- - lib/shaf_client/middleware/http_cache/base.rb
97
- - lib/shaf_client/middleware/http_cache/entry.rb
98
- - lib/shaf_client/middleware/http_cache/file_storage.rb
99
- - lib/shaf_client/middleware/http_cache/in_memory.rb
100
- - lib/shaf_client/middleware/http_cache/key.rb
101
- - lib/shaf_client/middleware/http_cache/query.rb
102
108
  - lib/shaf_client/middleware/redirect.rb
103
109
  - lib/shaf_client/resource.rb
104
110
  homepage: https://github.com/sammyhenningsson/shaf_client
metadata.gz.sig CHANGED
Binary file
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'shaf_client/middleware/http_cache/in_memory'
4
- require 'shaf_client/middleware/http_cache/file_storage'
5
- require 'shaf_client/middleware/http_cache/query'
6
- require 'shaf_client/middleware/http_cache/accessor'
7
-
8
- class ShafClient
9
- module Middleware
10
- class HttpCache
11
- Response = Struct.new(:status, :body, :headers, keyword_init: true)
12
-
13
- def initialize(app, cache_class: InMemory, accessed_by: nil, **options)
14
- @app = app
15
- @options = options
16
- @cache = cache_class.new(options)
17
- add_accessors_to accessed_by
18
- end
19
-
20
- def call(env)
21
- skip_cache = env[:request_headers].delete :skip_cache
22
- cached_entry = nil
23
-
24
- if cacheable?(env)
25
- query = Query.from(env)
26
- cache.load(query) do |cached|
27
- return cached_response(cached) if cached.valid? && !skip_cache
28
- add_etag(env, cached.etag)
29
- cached_entry = cached
30
- end
31
- end
32
-
33
- @app.call(env).on_complete do
34
- handle_not_modified(env, cached_entry)
35
- update_cache(env)
36
- end
37
- end
38
-
39
- private
40
-
41
- attr_reader :cache
42
-
43
- def add_accessors_to(obj)
44
- return unless obj
45
- obj.extend Accessor.for(cache)
46
- end
47
-
48
- def cacheable?(env)
49
- %i[get head].include? env[:method]
50
- end
51
-
52
- def cached_response(entry)
53
- cached_headers = entry.response_headers.transform_keys(&:to_s)
54
- Response.new(body: entry.payload, headers: cached_headers)
55
- end
56
-
57
- def add_etag(env, etag)
58
- env[:request_headers]['If-None-Match'] = etag if etag
59
- end
60
-
61
- def handle_not_modified(env, cached_entry)
62
- return unless env[:status] == 304
63
-
64
- cached_headers = cached_entry.response_headers.transform_keys(&:to_s)
65
- env[:body] = cached_entry.payload
66
- env[:response_headers] = cached_headers.merge(env[:response_headers])
67
-
68
- expire_at = Entry.from(env).expire_at
69
- cache.update_expiration(cached_entry, expire_at)
70
- end
71
-
72
- def update_cache(env)
73
- cache.inc_request_count
74
- entry = Entry.from(env)
75
- return unless storable?(env: env, entry: entry)
76
-
77
- cache.store entry
78
- end
79
-
80
- def storable?(env:, entry:)
81
- return false unless %i[get put].include? env[:method]
82
- return false unless env[:status] != 204
83
- return false unless (200..299).cover? env[:status]
84
- return false unless entry.etag || entry.expire_at
85
-
86
- request_headers = env.request_headers.transform_keys { |k| k.downcase.to_sym }
87
- entry.vary.keys.all? do |key|
88
- request_headers.include? key
89
- end
90
- end
91
- end
92
- end
93
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ShafClient
4
- module Middleware
5
- class HttpCache
6
- module Accessor
7
- module Accessible
8
- extend Forwardable
9
- def_delegator :__cache, :size, :cache_size
10
- def_delegator :__cache, :clear, :clear_cache
11
- def_delegator :__cache, :clear_stale, :clear_stale_cache
12
- end
13
-
14
- def self.for(cache)
15
- block = proc { cache }
16
-
17
- Module.new do
18
- include Accessible
19
- define_method(:__cache, &block)
20
- end
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,84 +0,0 @@
1
- require 'shaf_client/middleware/http_cache/entry'
2
- require 'shaf_client/middleware/http_cache/key'
3
-
4
- class ShafClient
5
- module Middleware
6
- class HttpCache
7
- class Base
8
- DEFAULT_PURGE_THRESHOLD = 1000
9
- DEFAULT_NO_REQUEST_BETWEEN_PURGE = 500
10
-
11
- attr_writer :request_count, :purge_threshold
12
-
13
- def initialize(**_options); end
14
-
15
- def purge_threshold
16
- @purge_threshold ||= DEFAULT_PURGE_THRESHOLD
17
- end
18
-
19
- def requests_between_purge
20
- DEFAULT_NO_REQUEST_BETWEEN_PURGE
21
- end
22
-
23
- def should_purge?
24
- (request_count % requests_between_purge).zero?
25
- end
26
-
27
- def inc_request_count
28
- self.request_count += 1
29
- return unless should_purge?
30
-
31
- clear_invalid
32
- purge
33
- end
34
-
35
- def request_count
36
- @request_count ||= 0
37
- end
38
-
39
- def purge_target
40
- (purge_threshold * 0.8).to_i
41
- end
42
-
43
- def purge
44
- return unless size > purge_threshold
45
-
46
- count = size - purge_target
47
- return unless count.positive?
48
-
49
- delete_if do
50
- break if count.zero?
51
- count -= 1
52
- end
53
- end
54
-
55
- def load(query)
56
- entry = get(query)
57
- return entry unless block_given?
58
- yield entry if entry
59
- end
60
-
61
- def store(entry)
62
- return unless entry.storable?
63
- put(entry)
64
- end
65
-
66
- def update_expiration(entry, expire_at)
67
- return unless expire_at
68
-
69
- updated_entry = entry.dup
70
- updated_entry.expire_at = expire_at
71
- store(updated_entry)
72
- end
73
-
74
- %i[size get put clear clear_invalid delete_if].each do |name|
75
- define_method(name) do
76
- raise NotImplementedError, "#{self.class} does not implement required method #{name}"
77
- end
78
- end
79
-
80
- private :delete_if
81
- end
82
- end
83
- end
84
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'time'
4
- require 'shaf_client/middleware/http_cache/key'
5
-
6
- class ShafClient
7
- module Middleware
8
- class HttpCache
9
- class Entry
10
- extend Key
11
-
12
- attr_reader :key, :payload, :etag, :vary, :response_headers
13
- attr_accessor :expire_at
14
-
15
- class << self
16
- def from(env)
17
- response_headers = response_headers(env)
18
-
19
- new(
20
- key: key(env.fetch(:url)),
21
- payload: env[:body],
22
- etag: response_headers[:etag],
23
- expire_at: expire_at(response_headers),
24
- vary: vary(env),
25
- response_headers: response_headers
26
- )
27
- end
28
-
29
- private
30
-
31
- def response_headers(env)
32
- response_headers = env.response_headers
33
- response_headers ||= {}
34
- response_headers.transform_keys { |k| k.downcase.to_sym }
35
- end
36
-
37
- def request_headers(env)
38
- request_headers = env.request_headers
39
- request_headers ||= {}
40
- request_headers.transform_keys { |k| k.downcase.to_sym }
41
- end
42
-
43
- def expire_at(headers)
44
- cache_control = headers[:'cache-control']
45
- return unless cache_control
46
-
47
- max_age = cache_control[/\bmax-age=(\d+)/, 1]
48
- Time.now + max_age.to_i if max_age
49
- end
50
-
51
- def vary(env)
52
- response_headers = response_headers(env)
53
- request_headers = request_headers(env)
54
- keys = response_headers.fetch(:vary, '').split(',')
55
- keys.each_with_object({}) do |key, vary|
56
- key = key.strip.downcase.to_sym
57
- # The respose that we see is already decoded (e.g. gunzipped) so
58
- # we shouldn't need to care about the Accept-Encoding header
59
- next if key == :'accept-encoding'
60
- vary[key] = request_headers[key]
61
- end
62
- end
63
- end
64
-
65
- def initialize(key:, payload: nil, etag: nil, expire_at: nil, vary: {}, response_headers: {})
66
- expire_at = Time.parse(expire_at) if expire_at.is_a? String
67
- @key = key.freeze
68
- @payload = payload.freeze
69
- @etag = etag.freeze
70
- @expire_at = expire_at.freeze
71
- @vary = vary.freeze
72
- @response_headers = response_headers
73
- freeze
74
- end
75
-
76
- def expired?
77
- not fresh?
78
- end
79
-
80
- def fresh?
81
- !!(expire_at && expire_at >= Time.now)
82
- end
83
-
84
- def valid?
85
- return false unless payload?
86
- fresh?
87
- end
88
-
89
- def invalid?
90
- !valid?
91
- end
92
-
93
- def payload?
94
- !!(payload && !payload.empty?)
95
- end
96
-
97
- def storable?
98
- return false unless payload?
99
- !!(etag || expire_at)
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,186 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tmpdir'
4
- require 'fileutils'
5
- require 'securerandom'
6
- require 'shaf_client/middleware/http_cache/base'
7
-
8
- class ShafClient
9
- module Middleware
10
- class HttpCache
11
- class FileStorage < Base
12
- class FileEntry < Entry
13
- attr_reader :filepath
14
-
15
- def self.deserialize(content)
16
- new(**JSON.parse(content, symbolize_names: true))
17
- end
18
-
19
- def self.from_entry(entry, filepath)
20
- new(
21
- key: entry.key,
22
- etag: entry.etag,
23
- expire_at: entry.expire_at,
24
- vary: entry.vary,
25
- payload: entry.payload,
26
- filepath: filepath,
27
- response_headers: entry.response_headers
28
- )
29
- end
30
-
31
- def initialize(
32
- key:,
33
- filepath:,
34
- payload: nil,
35
- etag: nil,
36
- expire_at: nil,
37
- vary: {},
38
- response_headers: {}
39
- )
40
- @filepath = filepath
41
- super(
42
- key: key,
43
- payload: payload,
44
- etag: etag,
45
- expire_at: expire_at,
46
- vary: vary,
47
- response_headers: response_headers
48
- )
49
- end
50
-
51
- def serialize
52
- JSON.pretty_generate(
53
- key: key,
54
- etag: etag,
55
- expire_at: expire_at,
56
- vary: vary,
57
- filepath: filepath,
58
- response_headers: response_headers,
59
- payload: payload
60
- )
61
- end
62
- end
63
-
64
- def initialize(**options)
65
- init_dir(options.delete(:directory))
66
- end
67
-
68
- def size
69
- count = 0
70
- each_file { count += 1 }
71
- count
72
- end
73
-
74
- def clear
75
- return unless Dir.exist? cache_dir
76
-
77
- FileUtils.remove_entry_secure cache_dir
78
- Dir.mkdir cache_dir
79
- end
80
-
81
- def clear_invalid
82
- delete_if(&:invalid?)
83
- end
84
-
85
- def get(query)
86
- find(query)
87
- end
88
-
89
- def put(entry)
90
- existing = find(Query.from(entry))
91
- unlink(existing) if existing
92
- write(entry)
93
- end
94
-
95
- def each_file
96
- return unless Dir.exist? cache_dir
97
-
98
- Dir.each_child(cache_dir) do |dir|
99
- Dir.each_child(File.join(cache_dir, dir)) do |file|
100
- yield File.join(cache_dir, dir, file)
101
- end
102
- end
103
- end
104
-
105
- def each
106
- each_file do |file|
107
- yield parse(file)
108
- end
109
- end
110
-
111
- private
112
-
113
- attr_reader :cache_dir
114
-
115
- def init_dir(dir)
116
- @cache_dir = String(dir)
117
- return if !@cache_dir.empty? && File.exist?(@cache_dir)
118
-
119
- @cache_dir = File.join(Dir.tmpdir, 'shaf_client_http_cache') if @cache_dir.empty?
120
- Dir.mkdir(@cache_dir) unless Dir.exist? @cache_dir
121
- end
122
-
123
- def delete_if
124
- each do |entry|
125
- unlink(entry) if yield entry
126
- end
127
- end
128
-
129
- def find(query)
130
- dir = dir(query.key)
131
- return unless dir && Dir.exist?(dir)
132
-
133
- Dir.each_child(dir) do |filename|
134
- path = File.join(dir, filename)
135
- file_entry = parse(path)
136
- return file_entry if query.match?(file_entry.vary)
137
- end
138
- end
139
-
140
- def dir(key)
141
- File.join(cache_dir, key.to_s.tr('/', '_'))
142
- end
143
-
144
- def parse(path)
145
- raise Error.new("File not readable: #{path}") unless File.readable? path
146
-
147
- content = File.read(path)
148
- FileEntry.deserialize(content)
149
- end
150
-
151
- def unlink(entry)
152
- File.unlink(entry.filepath) if entry.filepath
153
- dir = File.dirname(entry.filepath)
154
- Dir.delete(dir) if Dir.empty? dir
155
- end
156
-
157
- def write(entry)
158
- dir = dir(entry.key)
159
- raise Error.new("File not writable: #{dir}") unless File.writable? File.dirname(dir)
160
- Dir.mkdir(dir) unless Dir.exist?(dir)
161
-
162
- path = File.join(dir, filename(entry))
163
- raise Error.new("File not writable: #{dir}") unless File.writable? dir
164
- content = FileEntry.from_entry(entry, path).serialize
165
- File.write(path, content)
166
- end
167
-
168
- def filename(entry)
169
- [entry.expire_at, SecureRandom.hex(4)].join('_')
170
- end
171
- end
172
- end
173
- end
174
- end
175
- # shaf_client_http_cache
176
- # .
177
- # └── shaf_client_http_cache
178
- #    ├── host_posts
179
- #    │ ├── 2019-08-06T12:05:27_j2f
180
- #    │ ├── 2019-08-06T10:43:10_io1
181
- #    │ └── 2019-08-07T12:05:27_k13
182
- #    ├── host_posts_5
183
- #    │ └── 2019-08-07T22:12:23_kj1
184
- #    └── host_comments
185
- #    └── 2019-08-05T10:35:00_22m
186
-
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'shaf_client/middleware/http_cache/base'
4
-
5
- class ShafClient
6
- module Middleware
7
- class HttpCache
8
- class InMemory < Base
9
- def size
10
- mutex.synchronize do
11
- cache.sum { |_key, entries| entries.size }
12
- end
13
- end
14
-
15
- def clear
16
- mutex.synchronize { @cache = new_hash }
17
- end
18
-
19
- def clear_invalid
20
- mutex.synchronize do
21
- cache.each do |_key, entries|
22
- entries.keep_if(&:valid?)
23
- end
24
- end
25
- end
26
-
27
- def get(query)
28
- mutex.synchronize { find(query) }
29
- end
30
-
31
- def put(entry)
32
- mutex.synchronize do
33
- existing = find(Query.from(entry))
34
- cache[entry.key].delete(existing) if existing
35
- cache[entry.key].unshift entry
36
- end
37
- end
38
-
39
- private
40
-
41
- def mutex
42
- @mutex ||= Mutex.new
43
- end
44
-
45
- def cache
46
- @cache ||= new_hash
47
- end
48
-
49
- def new_hash
50
- Hash.new { |hash, key| hash[key] = [] }
51
- end
52
-
53
- def delete_if(&block)
54
- mutex.synchronize do
55
- cache.each do |_key, entries|
56
- entries.delete_if(&block)
57
- end
58
- end
59
- end
60
-
61
- def find(query)
62
- cache[query.key].find { |e| query.match?(e.vary) }
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ShafClient
4
- module Middleware
5
- class HttpCache
6
- module Key
7
- def key(uri)
8
- uri = URI(uri) if uri.is_a? String
9
- query = (uri.query || '').split('&').sort.join('&')
10
- [uri.host, uri.path, query].join('_').to_sym
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ShafClient
4
- module Middleware
5
- class HttpCache
6
- class Query
7
- extend Key
8
-
9
- attr_reader :key, :headers
10
-
11
- def self.from(env)
12
- return from_entry(env) if env.is_a? Entry
13
-
14
- new(
15
- key: key(env.fetch(:url)),
16
- headers: env.request_headers.transform_keys { |k| k.downcase.to_sym }
17
- )
18
- end
19
-
20
- def self.from_entry(entry)
21
- new(
22
- key: entry.key,
23
- headers: entry.vary
24
- )
25
- end
26
-
27
- def initialize(key:, headers: {})
28
- @key = key
29
- @headers = headers
30
- end
31
-
32
- def match?(vary)
33
- vary.all? do |key, value|
34
- headers[key] == value
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end