lucid_shopify 0.19.0 → 0.20.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: 959629883e5fa5237962423d070d6553aec6455b06854ec2385cc99e07d069b7
4
- data.tar.gz: 741fd2391fd995b4ddf48c59dd1cc428e81a6fd1c706980912de140172c548c3
3
+ metadata.gz: 2949f5da34bd486fe5ac4a76104577c730c5c086e36bde82c2bccc2925df3ade
4
+ data.tar.gz: c402dbe292c0b59a04e8b57d87b09017e666681e72dd99af180f7041a51c99d0
5
5
  SHA512:
6
- metadata.gz: 234a167e82f1a58d4f3f494350c6b77e186ca9b75bad804a736f609650137a6861f39dcde9d0d80dd3441f426d51a5c2604e30b33723825c65f215fb91ee7b73
7
- data.tar.gz: 02d491cea4b1b39c2ddd8a41963cf4ba3967d6c312b88da7eb333da2b52d41dc050974a250ebcf43418eb7139424692181fddaecad01a6bed608d64efa82c75f
6
+ metadata.gz: 1dc26e15a811b752753df1aeda9b1763de6edb8eef58e8277c691c9cf428578b7d0dd3742f03ff64d26d31eb04fd15f428ecfdec49b02f36763a23c2769969e4
7
+ data.tar.gz: efbdeeff6b99542cbe79ab72b0bcfdbd5b9f11c64ee19fc160fe80a5bc37c1aae6920b1b853463775a9b0d5b582fe9f5465c1ad1eb618b5b84935bec5df4888a
data/lib/lucid_shopify.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  require 'dry/initializer'
4
4
 
5
+ begin
6
+ require 'redis'
7
+ rescue LoadError
8
+ end
9
+
5
10
  module LucidShopify
6
11
  autoload :ActivateCharge, 'lucid_shopify/activate_charge'
7
12
  autoload :Authorize, 'lucid_shopify/authorize'
@@ -18,6 +23,7 @@ module LucidShopify
18
23
  autoload :GetRequest, 'lucid_shopify/get_request'
19
24
  autoload :PostRequest, 'lucid_shopify/post_request'
20
25
  autoload :PutRequest, 'lucid_shopify/put_request'
26
+ autoload :RedisThrottledStrategy, 'lucid_shopify/redis_throttled_strategy'
21
27
  autoload :Request, 'lucid_shopify/request'
22
28
  autoload :Response, 'lucid_shopify/response'
23
29
  autoload :Result, 'lucid_shopify/result'
@@ -20,7 +20,13 @@ module LucidShopify
20
20
  Container.register(:delete_webhook) { DeleteWebhook.new }
21
21
  Container.register(:http) { ::HTTP::Client.new }
22
22
  Container.register(:send_request) { SendRequest.new }
23
- Container.register(:send_throttled_request) { SendRequest.new(strategy: ThrottledStrategy.new) }
23
+ Container.register(:send_throttled_request) do
24
+ if defined?(Redis)
25
+ SendRequest.new(strategy: RedisThrottledStrategy.new)
26
+ else
27
+ SendRequest.new(strategy: ThrottledStrategy.new)
28
+ end
29
+ end
24
30
  Container.register(:verify_callback) { VerifyCallback.new }
25
31
  Container.register(:verify_webhook) { VerifyWebhook.new }
26
32
  Container.register(:webhook_handler_list) { LucidShopify.handlers }
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lucid_shopify'
4
+
5
+ if defined?(Redis)
6
+ module LucidShopify
7
+ #
8
+ # Use Redis to maintain API call limit throttling across threads/processes.
9
+ #
10
+ # No delay for requests up to half of the call limit.
11
+ #
12
+ class RedisThrottledStrategy < ThrottledStrategy
13
+ LEAK_RATE = 500
14
+
15
+ #
16
+ # @param redis_client [Redis]
17
+ #
18
+ def initialize(redis_client: Redis.current)
19
+ @redis_client = redis_client
20
+ end
21
+
22
+ #
23
+ # @param request [Request]
24
+ #
25
+ # @yieldreturn [Response]
26
+ #
27
+ # @return [Response]
28
+ #
29
+ def call(request, &send_request)
30
+ interval_key = build_interval_key(request)
31
+
32
+ interval(interval_key)
33
+
34
+ send_request.().tap do |res|
35
+ cur, max = res.headers['X-Shopify-Shop-Api-Call-Limit'].split('/')
36
+
37
+ @redis_client.mapped_hmset(interval_key,
38
+ cur: cur,
39
+ max: max,
40
+ at: timestamp
41
+ )
42
+ end
43
+ end
44
+
45
+ #
46
+ # If over half the call limit, sleep until requests leak back to the
47
+ # threshold.
48
+ #
49
+ # @param interval_key [String]
50
+ #
51
+ private def interval(interval_key)
52
+ cur, max, at = @redis_client.hmget(interval_key, :cur, :max, :at).map(&:to_i)
53
+
54
+ cur = leak(cur, at)
55
+
56
+ delay_threshold = max / 2 # no delay
57
+
58
+ if cur > delay_threshold
59
+ sleep(Rational((cur - delay_threshold) * LEAK_RATE, 1000))
60
+ end
61
+ end
62
+
63
+ #
64
+ # Find the actual value of {cur}, by subtracting requests leaked by the
65
+ # leaky bucket algorithm since the value was set.
66
+ #
67
+ # @param cur [Integer]
68
+ # @param at [Integer]
69
+ #
70
+ # @return [Integer]
71
+ #
72
+ private def leak(cur, at)
73
+ n = Rational(timestamp - at, LEAK_RATE).floor
74
+
75
+ n > cur ? 0 : cur - n
76
+ end
77
+ end
78
+ end
79
+ end
@@ -75,7 +75,7 @@ module LucidShopify
75
75
  req.object_id,
76
76
  req.http_method.to_s.upcase,
77
77
  req.url,
78
- req.options[:params]&.to_json || '{}'
78
+ req.options[:params]&.to_json || '{}',
79
79
  ])
80
80
  end
81
81
 
@@ -87,10 +87,11 @@ module LucidShopify
87
87
  req = request
88
88
  res = response
89
89
 
90
- LucidShopify.config.logger.info('<%s> [%i] %i' % [
90
+ LucidShopify.config.logger.info('<%s> [%i] %i (%s)' % [
91
91
  self.class.to_s,
92
92
  req.object_id,
93
- res.status_code
93
+ res.status_code,
94
+ res.headers['X-Shopify-Shop-Api-Call-Limit'],
94
95
  ])
95
96
  end
96
97
 
@@ -3,6 +3,9 @@
3
3
  require 'lucid_shopify'
4
4
 
5
5
  module LucidShopify
6
+ #
7
+ # Maintain API call limit throttling across a single thread.
8
+ #
6
9
  class ThrottledStrategy
7
10
  MINIMUM_INTERVAL = 500 # ms
8
11
 
@@ -20,13 +23,11 @@ module LucidShopify
20
23
  end
21
24
 
22
25
  #
23
- # Sleep for the difference if time since the last request is less than the
24
- # MINIMUM_INTERVAL.
26
+ # If time since the last request < {MINIMUM_INTERVAL}, then sleep for the
27
+ # difference.
25
28
  #
26
29
  # @param interval_key [String]
27
30
  #
28
- # @note Throttling is only maintained across a single thread.
29
- #
30
31
  private def interval(interval_key)
31
32
  if Thread.current[interval_key]
32
33
  (timestamp - Thread.current[interval_key]).tap do |n|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucidShopify
4
- VERSION = '0.19.0'
4
+ VERSION = '0.20.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lucid_shopify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kelsey Judson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-01 00:00:00.000000000 Z
11
+ date: 2018-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -132,6 +132,7 @@ files:
132
132
  - lib/lucid_shopify/get_request.rb
133
133
  - lib/lucid_shopify/post_request.rb
134
134
  - lib/lucid_shopify/put_request.rb
135
+ - lib/lucid_shopify/redis_throttled_strategy.rb
135
136
  - lib/lucid_shopify/request.rb
136
137
  - lib/lucid_shopify/response.rb
137
138
  - lib/lucid_shopify/send_request.rb