faraday_throttler 0.0.1

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 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: []