faraday_throttler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ecd40afc7d5acf7024059f4fcd820accc8da1bd1
4
+ data.tar.gz: 67e615a50d4067f00175991e00b1161b624bbb7a
5
+ SHA512:
6
+ metadata.gz: 4c3cc18b494510df0da3d56210bc1fee14518b1d9f9ff012d601af0dfcdfbccbee8c3d3a5bee0e4bf4c57a189dbd7b5f6dbc9939aa525072df4c28fcd73a6988
7
+ data.tar.gz: 026e803e53c5097b8c8623555932eb87826d7908ebdc209c3b81f1b96682fd9a9ebc642096a7c692999f489c15e3891a6e431cf4732aae3f7d92a086e668597c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in faraday_throttler.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'byebug'
8
+ end
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ [ ![Codeship Status for ismasan/faraday_throttler](https://codeship.com/projects/40d401a0-5c01-0133-561a-22b0ee77d2e6/status?branch=master)](https://codeship.com/projects/110895)
2
+
3
+ # FaradayThrottler
4
+
5
+ Configurable Faraday middleware for Ruby HTTP clients that:
6
+
7
+ * limits request rate to backend services.
8
+ * does its best to return cached or placeholder responses to clients while backend service is unavailable or slow.
9
+ * optionally uses Redis to rate-limit outgoing requests across processes and servers.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'faraday_throttler'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install faraday_throttler
26
+
27
+ ## Usage
28
+
29
+ ### Defaults
30
+
31
+ The defaul configuration use an in-memory lock and in-memory cache. Not suitable for multi-server deployments.
32
+
33
+ ```ruby
34
+ require 'faraday'
35
+ require 'faraday_throttler'
36
+
37
+ client = Faraday.new(:url => 'https://my.api.com') do |c|
38
+ c.use(
39
+ :throttler,
40
+ # Allow up to 1 request every 3 seconds, per path, to backend
41
+ rate: 3,
42
+ # Queued requests will wait for up to 2 seconds for current in-flight request
43
+ # to the same path.
44
+ # If in-flight request hasn't finished after that time, return a default placeholder response.
45
+ wait: 2
46
+ )
47
+ c.adapter Faraday.default_adapter
48
+ end
49
+ ```
50
+
51
+ Make some requests:
52
+
53
+ ```ruby
54
+ resp = client.get('/foobar')
55
+ resp.body
56
+ ```
57
+
58
+ The configuration above will only issue 1 request every 3 seconds to `my.api.com/foobar`. Requests to the same path will wait for up to 2 seconds for current _in-flight_ request to finish.
59
+
60
+ If an in-flight request finishes within that period, queued requests will respond with the same data.
61
+
62
+ If the in-flight request doesn't finish within 2 seconds, queued requests will attempt to serve a previous response from the same resource from cache.
63
+
64
+ If no matching response found in cache, a default fallback response will be used (status 204 No Content). Fallback responses can be cofigured.
65
+
66
+ Tweaking the `rate` and `wait` arguments allows you to control the rate of cached, fresh and fallback reponses.
67
+
68
+ ### Distributed Redis lock and cache
69
+
70
+ The defaults use in-memory lock and cache store implementations. To make the most efficient use of this gem across processes and servers, you can use [Redis](http://redis.io/) as a distributed lock and cache store.
71
+
72
+ ```ruby
73
+ require 'redis'
74
+ require 'faraday_throttler/redis_lock'
75
+ require 'faraday_throttler/redis_cache'
76
+
77
+ redis = Redis.new(uri: 'redis://my-redis-server.com:1234')
78
+
79
+ redis_lock = FaradayThrottler::RedisLock.new(redis)
80
+
81
+ # Cache entries will be available for 1 hour
82
+ redis_cache = FaradayThrottler::RedisCache.new(redis: redis, ttl: 3600)
83
+
84
+ client = Faraday.new(:url => 'https://my.api.com') do |c|
85
+ c.use(
86
+ :throttler,
87
+ rate: 3,
88
+ wait: 2,
89
+ # Use Redis-backed lock
90
+ lock: redis_lock,
91
+ # Use Redis-backed cache with set expiration
92
+ cache: redis_cache
93
+ )
94
+ c.adapter Faraday.default_adapter
95
+ end
96
+ ```
97
+
98
+ ## Advanced usage
99
+
100
+ Most internal behaviours are split into delegate objects that you can pass as middleware arguments to override the defaults. See the details [in the code](https://github.com/ismasan/faraday_throttler/blob/master/lib/faraday_throttler/middleware.rb#L16).
101
+
102
+ ## Development
103
+
104
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
105
+
106
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ismasan/faraday_throttler.
111
+
112
+ To contribute with code:
113
+
114
+ 1. Fork it ( http://github.com/ismasan/faraday_throttler/fork )
115
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
116
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
117
+ 4. Push to the branch (`git push origin my-new-feature`)
118
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "faraday_throttler"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/examples/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem 'redis'
5
+ gem 'rack'
6
+ gem 'faraday'
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ faraday (0.9.2)
5
+ multipart-post (>= 1.2, < 3)
6
+ multipart-post (2.0.0)
7
+ rack (1.6.4)
8
+ redis (3.2.1)
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ faraday
15
+ rack
16
+ redis
17
+
18
+ BUNDLED WITH
19
+ 1.10.6
@@ -0,0 +1,25 @@
1
+ ## Examples
2
+
3
+ ```
4
+ bundle install
5
+ ```
6
+
7
+ Run test rack server in one terminal window.
8
+
9
+ ```
10
+ rackup config.ru
11
+ ```
12
+
13
+ Run Redis in another:
14
+
15
+ ```
16
+ redis-server
17
+ ```
18
+
19
+ Run test client in another:
20
+
21
+ ```
22
+ ruby client.rb
23
+ ```
24
+
25
+ Tweak middleware option in `client.rb`
@@ -0,0 +1,30 @@
1
+ require 'bundler/setup'
2
+ require 'redis'
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'faraday_throttler/middleware'
5
+ require 'faraday_throttler/redis_lock'
6
+ require 'faraday_throttler/redis_cache'
7
+
8
+
9
+ redis = Redis.new
10
+ lock = FaradayThrottler::RedisLock.new(redis)
11
+ cache = FaradayThrottler::RedisCache.new(redis: redis, ttl: 60)
12
+
13
+ conn = Faraday.new(:url => 'http://localhost:9292') do |faraday|
14
+ # faraday.response :logger # log requests to STDOUT
15
+ faraday.use :throttler, rate: 3, wait: 1, lock: lock, cache: cache
16
+ faraday.adapter Faraday.default_adapter
17
+ end
18
+
19
+
20
+ tr = (1..100).map do |i|
21
+ Thread.new do
22
+ sleep (rand * 10)
23
+ n = Time.now
24
+ r = conn.get('/foo/bar')
25
+ puts %([#{n}] #{r.headers['X-Throttler']} took: #{r.headers['X-ThrottlerTime']} - #{r.body})
26
+ end
27
+ end
28
+
29
+ tr.map{|t| t.join }
30
+
@@ -0,0 +1,7 @@
1
+ require 'bundler/setup'
2
+
3
+ run ->(env) {
4
+ puts 'req'
5
+ sleep (rand * 5).ceil
6
+ [200, {'Content-Type' => 'application/json'}, [%({"date": "#{Time.now.to_s}"})]]
7
+ }
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'faraday_throttler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "faraday_throttler"
8
+ spec.version = FaradayThrottler::VERSION
9
+ spec.authors = ["Ismael Celis"]
10
+ spec.email = ["ismaelct@gmail.com"]
11
+
12
+ spec.summary = %q{Redis-backed request throttler requests to protect backend APIs against request stampedes}
13
+ spec.description = %q{Configure how often you want to hit backend APIs, and fallback responses to keep clients happy}
14
+ spec.homepage = "https://github.com/ismasan/faraday_throttler"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "faraday", ">= 0.9.1"
22
+ spec.add_development_dependency "bundler", "~> 1.9"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'faraday_throttler/retryable'
2
+
3
+ module FaradayThrottler
4
+ class Cache
5
+ include Retryable
6
+
7
+ def initialize(store = {})
8
+ @mutex = Mutex.new
9
+ @store = store
10
+ end
11
+
12
+ def set(key, resp)
13
+ mutex.synchronize { store[key] = resp }
14
+ end
15
+
16
+ def get(key, wait = 0)
17
+ with_retry(wait) {
18
+ mutex.synchronize { store[key] }
19
+ }
20
+ end
21
+
22
+ private
23
+ attr_reader :store, :mutex
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ module FaradayThrottler
2
+ module Errors
3
+ class ThrottlerError < StandardError; end
4
+ class SerializerError < ThrottlerError; end
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ module FaradayThrottler
2
+ class Fallbacks
3
+ DEFAULT_CONTENT_TYPE = 'application/json'.freeze
4
+
5
+ def call(req)
6
+ {
7
+ url: req[:url],
8
+ status: 204,
9
+ body: '',
10
+ response_headers: {
11
+ 'Content-Type' => req.fetch(:request_headers, {}).fetch('Content-Type', DEFAULT_CONTENT_TYPE)
12
+ }
13
+ }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module FaradayThrottler
2
+ class Gauge
3
+ def initialize(rate:, wait:)
4
+ @rate, @wait = rate, wait
5
+ end
6
+
7
+ def start(req_id, time = Time.now)
8
+
9
+ end
10
+
11
+ def update(req_id, state)
12
+
13
+ end
14
+
15
+ def finish(req_id, state)
16
+
17
+ end
18
+
19
+ def rate(req_id)
20
+ @rate
21
+ end
22
+
23
+ def wait(req_id)
24
+ @wait
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'openssl/digest'
2
+
3
+ module FaradayThrottler
4
+ class KeyResolver
5
+ def call(request_env)
6
+ hash request_env[:url].to_s
7
+ end
8
+
9
+ private
10
+ def hash(str)
11
+ OpenSSL::Digest::MD5.hexdigest(str)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module FaradayThrottler
2
+ class MemLock
3
+ def initialize
4
+ @locks = {}
5
+ @mutex = Mutex.new
6
+ end
7
+
8
+ def set(key, ttl = 30)
9
+ mutex.synchronize {
10
+ now = Time.now
11
+ exp = locks[key]
12
+
13
+ if !exp || exp < now
14
+ locks[key] = now + ttl
15
+ return true
16
+ else
17
+ return false
18
+ end
19
+ }
20
+ end
21
+
22
+ private
23
+ attr_reader :locks, :mutex
24
+
25
+ end
26
+ end
@@ -0,0 +1,177 @@
1
+ require 'timeout'
2
+ require 'faraday'
3
+ require 'faraday_throttler/key_resolver'
4
+ require 'faraday_throttler/mem_lock'
5
+ require 'faraday_throttler/cache'
6
+ require 'faraday_throttler/fallbacks'
7
+ require 'faraday_throttler/gauge'
8
+
9
+ module FaradayThrottler
10
+
11
+ class Middleware < Faraday::Middleware
12
+ def initialize(
13
+ # The base Faraday adapter.
14
+ app,
15
+
16
+ # Request lock. This checks that only one unique request is in-flight at a given time.
17
+ # Request uniqueness is defined by :lock_key_resolver.
18
+ # Interface:
19
+ # #set(key String, ttl Integer)
20
+ #
21
+ # Returns _true_ if new lock aquired (no previous in-flight request)
22
+ # Returns _false_ if no lock aquired (there is a current lock on an in-flight request).
23
+ # MemLock is an in-memory lock. On a multi-threaded / multi-process environment
24
+ # prefer the RedisLock implementation, which uses Redis as a distributed lock.
25
+ lock: MemLock.new,
26
+
27
+ # Response cache. Caches fresh responses from backend service,
28
+ # so they can be used as a first fallback when connection exceeds :wait time.
29
+ # Interface:
30
+ # #set(key String, response_env Hash)
31
+ # #get(key String, wait_seconds Integer)
32
+ #
33
+ # #get can implement polling/blocking behaviour
34
+ # to wait for inflight-request to populate cache
35
+ cache: Cache.new,
36
+
37
+ # Resolves request unique key to use as lock
38
+ # Interface:
39
+ # #call(request_env Hash) String
40
+ lock_key_resolver: KeyResolver.new,
41
+
42
+ # Resolves response unique key to use as cache key
43
+ # Interface:
44
+ # #call(response_env Hash) String
45
+ cache_key_resolver: KeyResolver.new,
46
+
47
+ # Allow up to 1 request every 10 seconds, per path, to backend
48
+ rate: 10,
49
+
50
+ # Queued requests will wait for up to 5 seconds for current in-flight request
51
+ # to the same path.
52
+ # If in-flight request hasn't finished after that time, return a default placeholder response.
53
+ wait: 5,
54
+
55
+ # Wraps requests to backend service in a timeout block, in seconds.
56
+ # If request takes longer than this:
57
+ # * `gauge` receives #update(req_id, :timeout)
58
+ # * Attempt to serve old response from cache. `gauge` receives #finish(req_id, :cached) if successful.
59
+ # * If no cached response, delegate to fallbacks#call(request_env). `gauge` receives #finish(req_id, :fallback)
60
+ # timeout: 0 disables this behaviour.
61
+ timeout: 0,
62
+
63
+ # Fallbacks resolver. Returns a fallback response when conection has waited over :wait time
64
+ # for an in-flight response.
65
+ # Use this to return sensible empty or error responses to your clients.
66
+ # Interface:
67
+ # #call(request_env Hash) response_env Hash
68
+ fallbacks: Fallbacks.new,
69
+
70
+ # Gauge exposes #rate and #wait, to be used as TTL for lock and cache wait time.
71
+ # The #start and #finish methods are called during a request/response cycle.
72
+ # This should allow custom gauges to implement their own heuristic to calculate #rate and #wait on the fly.
73
+ # By default a Null Gauge is used that just returns the values in the :rate and :wait arguments.
74
+ # Interface:
75
+ # #rate(request_id String) Integer
76
+ # #wait(request_id String) Integer
77
+ # #start(request_id String, start_time Time)
78
+ # #update(request_id String, state Symbol)
79
+ # #finish(request_id String, state Symbol)
80
+ #
81
+ # `request_id` is the result of cache_key_resolver#call, normally an MD5 hash of the request full URL.
82
+ # `state` can be one of :fresh, :cached, :timeout, :fallback
83
+ gauge: nil
84
+ )
85
+
86
+ validate_dep! lock, :lock, :set
87
+ validate_dep! cache, :cache, :get, :set
88
+ validate_dep! lock_key_resolver, :lock_key_resolver, :call
89
+ validate_dep! cache_key_resolver, :cache_key_resolver, :call
90
+ validate_dep! fallbacks, :fallbacks, :call
91
+
92
+ @lock = lock
93
+ @cache = cache
94
+ @lock_key_resolver = lock_key_resolver
95
+ @cache_key_resolver = cache_key_resolver
96
+ @rate = rate.to_i
97
+ @wait = wait.to_i
98
+ @timeout = timeout.to_i
99
+ @fallbacks = fallbacks
100
+ @gauge = gauge || Gauge.new(rate: @rate, wait: @wait)
101
+
102
+ validate_dep! @gauge, :gauge, :start, :update, :finish
103
+
104
+ super app
105
+ end
106
+
107
+ def call(request_env)
108
+ return app.call(request_env) if request_env[:method] != :get
109
+
110
+ start = Time.now
111
+
112
+ lock_key = lock_key_resolver.call(request_env)
113
+ cache_key = cache_key_resolver.call(request_env)
114
+
115
+ gauge.start cache_key, start
116
+
117
+ if lock.set(lock_key, gauge.rate(cache_key))
118
+ begin
119
+ with_timeout(timeout) {
120
+ app.call(request_env).on_complete do |response_env|
121
+ cache.set cache_key, response_env
122
+ gauge.finish cache_key, :fresh
123
+ debug_headers response_env, :fresh, start
124
+ end
125
+ }
126
+ rescue ::Timeout::Error => e
127
+ gauge.update cache_key, :timeout
128
+ serve_from_cache_or_fallback request_env, cache_key, start
129
+ end
130
+ else
131
+ serve_from_cache_or_fallback request_env, cache_key, start
132
+ end
133
+ end
134
+
135
+ private
136
+ attr_reader :app, :lock, :cache, :lock_key_resolver, :cache_key_resolver, :rate, :wait, :timeout, :fallbacks, :gauge
137
+
138
+ def serve_from_cache_or_fallback(request_env, cache_key, start)
139
+ if cached_response = cache.get(cache_key, gauge.wait(cache_key))
140
+ gauge.finish cache_key, :cached
141
+ resp cached_response, :cached, start
142
+ else
143
+ gauge.finish cache_key, :fallback
144
+ resp fallbacks.call(request_env), :fallback, start
145
+ end
146
+ end
147
+
148
+ def resp(resp_env, status = :fresh, start = Time.now)
149
+ resp_env = Faraday::Env.from(resp_env)
150
+ debug_headers resp_env, status, start
151
+ ::Faraday::Response.new(resp_env)
152
+ end
153
+
154
+ def validate_dep!(dep, dep_name, *methods)
155
+ methods.each do |m|
156
+ raise ArgumentError, %(#{dep_name} must implement :#{m}) unless dep.respond_to?(m)
157
+ end
158
+ end
159
+
160
+ def debug_headers(resp_env, status, start)
161
+ resp_env[:response_headers].merge!(
162
+ 'X-Throttler' => status.to_s,
163
+ 'X-ThrottlerTime' => (Time.now - start)
164
+ )
165
+ end
166
+
167
+ def with_timeout(seconds, &block)
168
+ if seconds == 0
169
+ yield
170
+ else
171
+ ::Timeout.timeout(seconds, &block)
172
+ end
173
+ end
174
+ end
175
+
176
+ Faraday::Middleware.register_middleware throttler: ->{ Middleware }
177
+ end
@@ -0,0 +1,32 @@
1
+ require 'faraday_throttler/retryable'
2
+ require 'faraday_throttler/serializer'
3
+
4
+ module FaradayThrottler
5
+ class RedisCache
6
+ NAMESPACE = 'throttler:cache:'.freeze
7
+
8
+ include Retryable
9
+
10
+ def initialize(redis: Redis.new, ttl: 0, serializer: Serializer.new)
11
+ @redis = redis
12
+ @ttl = ttl
13
+ @serializer = serializer
14
+ end
15
+
16
+ def set(key, resp)
17
+ opts = {}
18
+ opts[:ex] = ttl if ttl > 0
19
+ redis.set [NAMESPACE, key].join, serializer.serialize(resp), opts
20
+ end
21
+
22
+ def get(key, wait = 10)
23
+ with_retry(wait) {
24
+ r = redis.get([NAMESPACE, key].join)
25
+ r ? serializer.deserialize(r) : nil
26
+ }
27
+ end
28
+
29
+ private
30
+ attr_reader :redis, :ttl, :serializer
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ module FaradayThrottler
2
+ class RedisLock
3
+ NAMESPACE = 'throttler:lock:'.freeze
4
+
5
+ def initialize(redis = Redis.new)
6
+ @redis = redis
7
+ end
8
+
9
+ def set(key, ttl = 30)
10
+ redis.set([NAMESPACE, key].join, '1', ex: ttl, nx: true)
11
+ end
12
+
13
+ private
14
+ attr_reader :redis
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module FaradayThrottler
2
+ module Retryable
3
+ private
4
+
5
+ def with_retry(wait, &block)
6
+ r = block.call
7
+ return r if r || wait == 0
8
+
9
+ value = nil
10
+ ticks = 0
11
+ while ticks <= wait do
12
+ Kernel.sleep 1
13
+ ticks += 1
14
+ value = block.call
15
+ end
16
+
17
+ value
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+ require 'faraday_throttler/errors'
3
+
4
+ module FaradayThrottler
5
+ class Serializer
6
+ def serialize(resp)
7
+ validate_response! resp
8
+
9
+ hash = {
10
+ status: resp[:status],
11
+ body: resp[:body],
12
+ response_headers: resp[:response_headers]
13
+ }
14
+
15
+ JSON.dump hash
16
+ end
17
+
18
+ def deserialize(json)
19
+ JSON.parse(json.to_s)
20
+ rescue JSON::ParserError => e
21
+ raise Errors::SerializerError, e.message
22
+ end
23
+
24
+ private
25
+ def validate_response!(resp)
26
+ unless resp.has_key?(:status) && resp.has_key?(:body) && resp.has_key?(:response_headers)
27
+ raise Errors::SerializerError, "response is not valid. Fields: #{resp.keys}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module FaradayThrottler
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "faraday_throttler/version"
2
+
3
+ module FaradayThrottler
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'faraday_throttler/middleware'
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faraday_throttler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ismael Celis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Configure how often you want to hit backend APIs, and fallback responses
70
+ to keep clients happy
71
+ email:
72
+ - ismaelct@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - examples/Gemfile
86
+ - examples/Gemfile.lock
87
+ - examples/README.md
88
+ - examples/client.rb
89
+ - examples/config.ru
90
+ - faraday_throttler.gemspec
91
+ - lib/faraday_throttler.rb
92
+ - lib/faraday_throttler/cache.rb
93
+ - lib/faraday_throttler/errors.rb
94
+ - lib/faraday_throttler/fallbacks.rb
95
+ - lib/faraday_throttler/gauge.rb
96
+ - lib/faraday_throttler/key_resolver.rb
97
+ - lib/faraday_throttler/mem_lock.rb
98
+ - lib/faraday_throttler/middleware.rb
99
+ - lib/faraday_throttler/redis_cache.rb
100
+ - lib/faraday_throttler/redis_lock.rb
101
+ - lib/faraday_throttler/retryable.rb
102
+ - lib/faraday_throttler/serializer.rb
103
+ - lib/faraday_throttler/version.rb
104
+ homepage: https://github.com/ismasan/faraday_throttler
105
+ licenses: []
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.8
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Redis-backed request throttler requests to protect backend APIs against request
127
+ stampedes
128
+ test_files: []