contentful 0.11.0 → 0.12.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
  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