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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -2
- data/lib/shaf_client.rb +7 -8
- data/lib/shaf_client/resource.rb +1 -1
- metadata +16 -10
- metadata.gz.sig +0 -0
- data/lib/shaf_client/middleware/http_cache.rb +0 -93
- data/lib/shaf_client/middleware/http_cache/accessor.rb +0 -25
- data/lib/shaf_client/middleware/http_cache/base.rb +0 -84
- data/lib/shaf_client/middleware/http_cache/entry.rb +0 -104
- data/lib/shaf_client/middleware/http_cache/file_storage.rb +0 -186
- data/lib/shaf_client/middleware/http_cache/in_memory.rb +0 -67
- data/lib/shaf_client/middleware/http_cache/key.rb +0 -15
- data/lib/shaf_client/middleware/http_cache/query.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c7758e0a388759de54cd305d5b1541c01c071d0155939fca0dd8a2f9fb57438
|
4
|
+
data.tar.gz: c297702586a42855ec25e3f8e0d9fef33936393b3ade5b3258a2a89b03b85896
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
2
|
-
|
1
|
+
2�ӳ�&;M����ťPY�����"��$y��J��<uVZ�Y���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)�/>���5�J着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
|
88
|
-
options.
|
89
|
-
|
90
|
-
|
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
|
data/lib/shaf_client/resource.rb
CHANGED
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
|
+
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-
|
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
|