shaf_client 0.4.0 → 0.5.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: 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