contentful 0.11.0 → 0.12.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
  SHA1:
3
- metadata.gz: 4ba77ca80d0071ad28546271729538c6a701616d
4
- data.tar.gz: 65a9ee25b3709bb641deed8345862914480068e7
3
+ metadata.gz: 78c2a767a88bc7cf15bde4fcc9173611d0c2a116
4
+ data.tar.gz: d99bee10915e4128e254b9f41fb4ad8472ec2f83
5
5
  SHA512:
6
- metadata.gz: 51c966da267b2dc62c189a57dd69200ec9a83f81cba5bccd4695c5ef756babdc6aeccbb53a7c98bde97325fbfff86c68eac08912871192d035d36188828de389
7
- data.tar.gz: d5c14ff5244ef3463bfec9cac959be1de527f45ca0f48e8885101ea473dc5f53f1b8d471a2479b668504cc8ee003ccb23ea4be549a3e5aee1135929f03039c91
6
+ metadata.gz: e0118fccbdb2a308c14b89748a5f9fdc42f32bf1593e6917897e2edb0613ea98843fdb47dda3642a722a475dad7f75df4297c9500638e185e14109b52a829532
7
+ data.tar.gz: e6c7aff051696aa83e5c9fa0d52c021bffec3535c04e45650f0b00d4dcf4364f0d18089010af3fd5b11153851b25e64c59e9e359eb75da7034d51fce7f40e394
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Change Log
2
2
  ## Unreleased
3
3
 
4
+ ## 0.12.0
5
+
6
+ ### Added
7
+ * Added Rate Limit automatic handling
8
+
4
9
  ## 0.11.0
5
10
  ### Fixed
6
11
  * Fixed Locale handling [#98](https://github.com/contentful/contentful.rb/issues/98)
data/README.md CHANGED
@@ -238,6 +238,17 @@ Specify the port number that is used by the proxy server for client connections.
238
238
 
239
239
  To use the proxy with authentication, you need to specify ```port_username``` and ```port_password```.
240
240
 
241
+ ### :max_rate_limit_retries
242
+
243
+ To increase or decrease the retry attempts after a 429 Rate Limit error. Default value is 1. Using 0 will disable retry behaviour.
244
+ Each retry will be attempted after the value (in seconds) of the `X-Contentful-RateLimit-Reset` header, which contains the amount of seconds until the next
245
+ non rate limited request is available, has passed. This is blocking per execution thread.
246
+
247
+ ### :max_rate_limit_wait
248
+
249
+ Maximum time to wait for next available request (in seconds). Default value is 60 seconds. Keep in mind that if you hit the houly rate limit maximum, you
250
+ can have up to 60 minutes of blocked requests. It is set to a default of 60 seconds in order to avoid blocking processes for too long, as rate limit retry behaviour
251
+ is blocking per execution thread.
241
252
 
242
253
 
243
254
  ### Proxy example
@@ -30,8 +30,12 @@ module Contentful
30
30
  proxy_host: nil,
31
31
  proxy_username: nil,
32
32
  proxy_password: nil,
33
- proxy_port: nil
33
+ proxy_port: nil,
34
+ max_rate_limit_retries: 1,
35
+ max_rate_limit_wait: 60
34
36
  }
37
+ # Rate Limit Reset Header Key
38
+ RATE_LIMIT_RESET_HEADER_KEY = 'x-contentful-ratelimit-reset'
35
39
 
36
40
  attr_reader :configuration, :dynamic_entry_cache, :logger, :proxy
37
41
 
@@ -56,6 +60,8 @@ module Contentful
56
60
  # @option given_configuration [String] :proxy_username
57
61
  # @option given_configuration [String] :proxy_password
58
62
  # @option given_configuration [Number] :proxy_port
63
+ # @option given_configuration [Number] :max_rate_limit_retries
64
+ # @option given_configuration [Number] :max_rate_limit_wait
59
65
  # @option given_configuration [Boolean] :gzip_encoded
60
66
  # @option given_configuration [Boolean] :raw_mode
61
67
  # @option given_configuration [false, ::Logger] :logger
@@ -196,9 +202,44 @@ module Contentful
196
202
  #
197
203
  # @private
198
204
  def get(request, build_resource = true)
205
+ retries_left = configuration[:max_rate_limit_retries]
206
+ begin
207
+ response = run_request(request)
208
+
209
+ return response if !build_resource || configuration[:raw_mode]
210
+
211
+ result = do_build_resource(response)
212
+
213
+ fail result if result.is_a?(Error) && configuration[:raise_errors]
214
+ rescue Contentful::RateLimitExceeded => rate_limit_error
215
+ reset_time = rate_limit_error.response.raw[RATE_LIMIT_RESET_HEADER_KEY].to_i
216
+ if should_retry(retries_left, reset_time, configuration[:max_rate_limit_wait])
217
+ retries_left -= 1
218
+ retry_message = 'Contentful API Rate Limit Hit! '
219
+ retry_message += "Retrying - Retries left: #{retries_left}"
220
+ retry_message += "- Time until reset (seconds): #{reset_time}"
221
+ logger.info(retry_message) if logger
222
+ sleep(reset_time * Random.new.rand(1.0..1.2))
223
+ retry
224
+ end
225
+
226
+ raise
227
+ end
228
+
229
+ result
230
+ end
231
+
232
+ # @private
233
+ def should_retry(retries_left, reset_time, max_wait)
234
+ retries_left > 0 && max_wait > reset_time
235
+ end
236
+
237
+ # Runs request and parses Response
238
+ # @private
239
+ def run_request(request)
199
240
  url = request.absolute? ? request.url : base_url + request.url
200
241
  logger.info(request: { url: url, query: request.query, header: request_headers }) if logger
201
- response = Response.new(
242
+ Response.new(
202
243
  self.class.get_http(
203
244
  url,
204
245
  request_query(request.query),
@@ -206,19 +247,19 @@ module Contentful
206
247
  proxy_params
207
248
  ), request
208
249
  )
250
+ end
209
251
 
210
- return response if !build_resource || configuration[:raw_mode]
252
+ # Runs Resource Builder
253
+ # @private
254
+ def do_build_resource(response)
211
255
  logger.debug(response: response) if logger
212
- result = configuration[:resource_builder].new(
256
+ configuration[:resource_builder].new(
213
257
  self,
214
258
  response,
215
259
  configuration[:resource_mapping],
216
260
  configuration[:entry_mapping],
217
261
  configuration[:default_locale]
218
262
  ).run
219
-
220
- fail result if result.is_a?(Error) && configuration[:raise_errors]
221
- result
222
263
  end
223
264
 
224
265
  # Use this method together with the client's :dynamic_entries configuration.
@@ -226,12 +267,12 @@ module Contentful
226
267
  # @private
227
268
  def update_dynamic_entry_cache!
228
269
  @dynamic_entry_cache = Hash[
229
- content_types(limit: 1000).map do |ct|
230
- [
231
- ct.id.to_sym,
232
- DynamicEntry.create(ct)
233
- ]
234
- end
270
+ content_types(limit: 1000).map do |ct|
271
+ [
272
+ ct.id.to_sym,
273
+ DynamicEntry.create(ct)
274
+ ]
275
+ end
235
276
  ]
236
277
  end
237
278
 
@@ -1,5 +1,5 @@
1
1
  # Contentful Namespace
2
2
  module Contentful
3
3
  # Gem Version
4
- VERSION = '0.11.0'
4
+ VERSION = '0.12.0'
5
5
  end
@@ -1,5 +1,27 @@
1
1
  require 'spec_helper'
2
2
 
3
+ class NonCachingClient < Contentful::Client
4
+ def request_headers
5
+ headers = super
6
+ headers['Cf-No-Cache'] = 'foobar'
7
+ headers
8
+ end
9
+ end
10
+
11
+ class RetryLoggerMock < Logger
12
+ attr_reader :retry_attempts
13
+
14
+ def initialize(*)
15
+ super
16
+ @retry_attempts = 0
17
+ end
18
+
19
+ def info(message)
20
+ super
21
+ @retry_attempts += 1 if message.include?('Contentful API Rate Limit Hit! Retrying')
22
+ end
23
+ end
24
+
3
25
  describe 'Error Requests' do
4
26
  it 'will return 404 (Unauthorized) if resource not found' do
5
27
  expect_vcr('not found')do
@@ -29,13 +51,32 @@ describe 'Error Requests' do
29
51
  skip
30
52
  end
31
53
 
32
- it 'will return a 429 if the ratelimit is reached' do
33
- client = Contentful::Client.new(space: 'wrong', access_token: 'credentials')
54
+ it 'will return a 429 if the ratelimit is reached and is not set to retry' do
55
+ client = Contentful::Client.new(space: 'wrong', access_token: 'credentials', max_rate_limit_retries: 0)
34
56
  expect_vcr('ratelimit') {
35
57
  client.entry('nyancat')
36
58
  }.to raise_error(Contentful::RateLimitExceeded)
37
59
  end
38
60
 
61
+ it 'will retry on 429 by default' do
62
+ logger = RetryLoggerMock.new(STDOUT)
63
+ client = NonCachingClient.new(
64
+ api_url: 'cdnorigin.flinkly.com',
65
+ space: '164vhtp008kz',
66
+ access_token: '7699b6c6f6cee9b6abaa216c71fbcb3eee56cb6f082f57b5e21b2b50f86bdea0',
67
+ raise_errors: true,
68
+ logger: logger
69
+ )
70
+
71
+ vcr('ratelimit_retry') {
72
+ 3.times {
73
+ client.assets
74
+ }
75
+ }
76
+
77
+ expect(logger.retry_attempts).to eq 1
78
+ end
79
+
39
80
  it 'will return 503 (ServiceUnavailable) when the service is unavailable' do
40
81
  client = Contentful::Client.new(space: 'wrong', access_token: 'credentials')
41
82
 
@@ -0,0 +1,294 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://cdnorigin.flinkly.com/spaces/164vhtp008kz/assets
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - RubyContentfulGem/0.10.0
12
+ Authorization:
13
+ - Bearer 7699b6c6f6cee9b6abaa216c71fbcb3eee56cb6f082f57b5e21b2b50f86bdea0
14
+ Content-Type:
15
+ - application/vnd.contentful.delivery.v1+json
16
+ Accept-Encoding:
17
+ - gzip
18
+ Cf-No-Cache:
19
+ - foobar
20
+ Connection:
21
+ - close
22
+ Host:
23
+ - cdnorigin.flinkly.com
24
+ response:
25
+ status:
26
+ code: 429
27
+ message: Too Many Requests
28
+ headers:
29
+ Accept-Ranges:
30
+ - bytes
31
+ Access-Control-Allow-Headers:
32
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent
33
+ Access-Control-Allow-Methods:
34
+ - GET,HEAD,OPTIONS
35
+ Access-Control-Allow-Origin:
36
+ - "*"
37
+ Access-Control-Max-Age:
38
+ - '86400'
39
+ Cache-Control:
40
+ - max-age=0
41
+ Content-Type:
42
+ - application/vnd.contentful.management.v1+json
43
+ Date:
44
+ - Tue, 09 Aug 2016 08:46:00 GMT
45
+ Server:
46
+ - nginx
47
+ X-Contentful-Ratelimit-Reset:
48
+ - '1'
49
+ Content-Length:
50
+ - '304'
51
+ Connection:
52
+ - Close
53
+ body:
54
+ encoding: UTF-8
55
+ string: |
56
+ {
57
+ "sys": {
58
+ "type": "Error",
59
+ "id": "RateLimitExceeded"
60
+ },
61
+ "message": "You have exceeded the rate limit of the Organization this Space belongs to by making too many API requests within a short timespan. Please wait a moment before trying the request again.",
62
+ "requestId": "395-766168967"
63
+ }
64
+ http_version:
65
+ recorded_at: Tue, 09 Aug 2016 08:46:00 GMT
66
+ - request:
67
+ method: get
68
+ uri: https://cdnorigin.flinkly.com/spaces/164vhtp008kz/assets
69
+ body:
70
+ encoding: US-ASCII
71
+ string: ''
72
+ headers:
73
+ User-Agent:
74
+ - RubyContentfulGem/0.10.0
75
+ Authorization:
76
+ - Bearer 7699b6c6f6cee9b6abaa216c71fbcb3eee56cb6f082f57b5e21b2b50f86bdea0
77
+ Content-Type:
78
+ - application/vnd.contentful.delivery.v1+json
79
+ Accept-Encoding:
80
+ - gzip
81
+ Cf-No-Cache:
82
+ - foobar
83
+ Connection:
84
+ - close
85
+ Host:
86
+ - cdnorigin.flinkly.com
87
+ response:
88
+ status:
89
+ code: 200
90
+ message: OK
91
+ headers:
92
+ Accept-Ranges:
93
+ - bytes
94
+ Access-Control-Allow-Headers:
95
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent
96
+ Access-Control-Allow-Methods:
97
+ - GET,HEAD,OPTIONS
98
+ Access-Control-Allow-Origin:
99
+ - "*"
100
+ Access-Control-Expose-Headers:
101
+ - Etag
102
+ Access-Control-Max-Age:
103
+ - '86400'
104
+ Cache-Control:
105
+ - max-age=0
106
+ Content-Type:
107
+ - application/vnd.contentful.delivery.v1+json
108
+ Date:
109
+ - Tue, 09 Aug 2016 08:46:00 GMT
110
+ Etag:
111
+ - '"29f2c21be26360c424f617d8592cf6f9"'
112
+ Server:
113
+ - nginx
114
+ Surrogate-Control:
115
+ - max-age=21600
116
+ Surrogate-Key:
117
+ - space-164vhtp008kz organization-7Kp22ygMRaA3DCjACF64PF
118
+ Vary:
119
+ - Accept-Encoding
120
+ X-Content-Type-Options:
121
+ - nosniff
122
+ X-Contentful-Request-Id:
123
+ - 395-766168968
124
+ Content-Length:
125
+ - '97'
126
+ Connection:
127
+ - Close
128
+ body:
129
+ encoding: UTF-8
130
+ string: |
131
+ {
132
+ "sys": {
133
+ "type": "Array"
134
+ },
135
+ "total": 0,
136
+ "skip": 0,
137
+ "limit": 100,
138
+ "items": []
139
+ }
140
+ http_version:
141
+ recorded_at: Tue, 09 Aug 2016 08:46:00 GMT
142
+ - request:
143
+ method: get
144
+ uri: https://cdnorigin.flinkly.com/spaces/164vhtp008kz/assets
145
+ body:
146
+ encoding: US-ASCII
147
+ string: ''
148
+ headers:
149
+ User-Agent:
150
+ - RubyContentfulGem/0.10.0
151
+ Authorization:
152
+ - Bearer 7699b6c6f6cee9b6abaa216c71fbcb3eee56cb6f082f57b5e21b2b50f86bdea0
153
+ Content-Type:
154
+ - application/vnd.contentful.delivery.v1+json
155
+ Accept-Encoding:
156
+ - gzip
157
+ Cf-No-Cache:
158
+ - foobar
159
+ Connection:
160
+ - close
161
+ Host:
162
+ - cdnorigin.flinkly.com
163
+ response:
164
+ status:
165
+ code: 200
166
+ message: OK
167
+ headers:
168
+ Accept-Ranges:
169
+ - bytes
170
+ Access-Control-Allow-Headers:
171
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent
172
+ Access-Control-Allow-Methods:
173
+ - GET,HEAD,OPTIONS
174
+ Access-Control-Allow-Origin:
175
+ - "*"
176
+ Access-Control-Expose-Headers:
177
+ - Etag
178
+ Access-Control-Max-Age:
179
+ - '86400'
180
+ Cache-Control:
181
+ - max-age=0
182
+ Content-Type:
183
+ - application/vnd.contentful.delivery.v1+json
184
+ Date:
185
+ - Tue, 09 Aug 2016 08:46:00 GMT
186
+ Etag:
187
+ - '"29f2c21be26360c424f617d8592cf6f9"'
188
+ Server:
189
+ - nginx
190
+ Surrogate-Control:
191
+ - max-age=21600
192
+ Surrogate-Key:
193
+ - space-164vhtp008kz organization-7Kp22ygMRaA3DCjACF64PF
194
+ Vary:
195
+ - Accept-Encoding
196
+ X-Content-Type-Options:
197
+ - nosniff
198
+ X-Contentful-Request-Id:
199
+ - 395-766168969
200
+ Content-Length:
201
+ - '97'
202
+ Connection:
203
+ - Close
204
+ body:
205
+ encoding: UTF-8
206
+ string: |
207
+ {
208
+ "sys": {
209
+ "type": "Array"
210
+ },
211
+ "total": 0,
212
+ "skip": 0,
213
+ "limit": 100,
214
+ "items": []
215
+ }
216
+ http_version:
217
+ recorded_at: Tue, 09 Aug 2016 08:46:00 GMT
218
+ - request:
219
+ method: get
220
+ uri: https://cdnorigin.flinkly.com/spaces/164vhtp008kz/assets
221
+ body:
222
+ encoding: US-ASCII
223
+ string: ''
224
+ headers:
225
+ User-Agent:
226
+ - RubyContentfulGem/0.10.0
227
+ Authorization:
228
+ - Bearer 7699b6c6f6cee9b6abaa216c71fbcb3eee56cb6f082f57b5e21b2b50f86bdea0
229
+ Content-Type:
230
+ - application/vnd.contentful.delivery.v1+json
231
+ Accept-Encoding:
232
+ - gzip
233
+ Cf-No-Cache:
234
+ - foobar
235
+ Connection:
236
+ - close
237
+ Host:
238
+ - cdnorigin.flinkly.com
239
+ response:
240
+ status:
241
+ code: 200
242
+ message: OK
243
+ headers:
244
+ Accept-Ranges:
245
+ - bytes
246
+ Access-Control-Allow-Headers:
247
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent
248
+ Access-Control-Allow-Methods:
249
+ - GET,HEAD,OPTIONS
250
+ Access-Control-Allow-Origin:
251
+ - "*"
252
+ Access-Control-Expose-Headers:
253
+ - Etag
254
+ Access-Control-Max-Age:
255
+ - '86400'
256
+ Cache-Control:
257
+ - max-age=0
258
+ Content-Type:
259
+ - application/vnd.contentful.delivery.v1+json
260
+ Date:
261
+ - Tue, 09 Aug 2016 08:46:02 GMT
262
+ Etag:
263
+ - '"29f2c21be26360c424f617d8592cf6f9"'
264
+ Server:
265
+ - nginx
266
+ Surrogate-Control:
267
+ - max-age=21600
268
+ Surrogate-Key:
269
+ - space-164vhtp008kz organization-7Kp22ygMRaA3DCjACF64PF
270
+ Vary:
271
+ - Accept-Encoding
272
+ X-Content-Type-Options:
273
+ - nosniff
274
+ X-Contentful-Request-Id:
275
+ - 395-766168970
276
+ Content-Length:
277
+ - '97'
278
+ Connection:
279
+ - Close
280
+ body:
281
+ encoding: UTF-8
282
+ string: |
283
+ {
284
+ "sys": {
285
+ "type": "Array"
286
+ },
287
+ "total": 0,
288
+ "skip": 0,
289
+ "limit": 100,
290
+ "items": []
291
+ }
292
+ http_version:
293
+ recorded_at: Tue, 09 Aug 2016 08:46:02 GMT
294
+ recorded_with: VCR 3.0.3
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contentful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Contentful GmbH (Jan Lelis)
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-08-09 00:00:00.000000000 Z
12
+ date: 2016-08-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: http
@@ -400,6 +400,7 @@ files:
400
400
  - spec/fixtures/vcr_cassettes/not_found.yml
401
401
  - spec/fixtures/vcr_cassettes/nyancat.yml
402
402
  - spec/fixtures/vcr_cassettes/ratelimit.yml
403
+ - spec/fixtures/vcr_cassettes/ratelimit_retry.yml
403
404
  - spec/fixtures/vcr_cassettes/reloaded_entry.yml
404
405
  - spec/fixtures/vcr_cassettes/space.yml
405
406
  - spec/fixtures/vcr_cassettes/sync_deleted_asset.yml
@@ -493,6 +494,7 @@ test_files:
493
494
  - spec/fixtures/vcr_cassettes/not_found.yml
494
495
  - spec/fixtures/vcr_cassettes/nyancat.yml
495
496
  - spec/fixtures/vcr_cassettes/ratelimit.yml
497
+ - spec/fixtures/vcr_cassettes/ratelimit_retry.yml
496
498
  - spec/fixtures/vcr_cassettes/reloaded_entry.yml
497
499
  - spec/fixtures/vcr_cassettes/space.yml
498
500
  - spec/fixtures/vcr_cassettes/sync_deleted_asset.yml