chokepoint 0.1.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.
- data/LICENSE +16 -0
- data/README.md +81 -0
- data/lib/chokepoint.rb +11 -0
- data/lib/chokepoint/daily.rb +44 -0
- data/lib/chokepoint/hourly.rb +44 -0
- data/lib/chokepoint/interval.rb +52 -0
- data/lib/chokepoint/limiter.rb +155 -0
- data/lib/chokepoint/minute.rb +43 -0
- data/lib/chokepoint/time_window.rb +21 -0
- data/lib/chokepoint/version.rb +13 -0
- metadata +104 -0
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
The MIT License (MIT) Copyright (c) 2011 Viximo, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
4
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
7
|
+
furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
10
|
+
portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
Chokepoint: Rate Limiter
|
2
|
+
========================
|
3
|
+
|
4
|
+
This library provides throttling for arbitary blocks. Chokepoint is based on Arto Bendiken's
|
5
|
+
rack-throttle library.
|
6
|
+
|
7
|
+
* Chokepoint <http://github.com/Viximo/chokepoint>
|
8
|
+
* Rack::Throttle <http://github.com/bendiken/rack-throttle>
|
9
|
+
|
10
|
+
Features
|
11
|
+
--------
|
12
|
+
|
13
|
+
* Throttles a block by enforcing a minimum time interval between
|
14
|
+
subsequent calls or by enforcing a maximum number of calls in a given
|
15
|
+
duration (Daily, Hourly, Minute)
|
16
|
+
* Stores rate-limiting counters in any key/value store implementation that
|
17
|
+
responds to `#[]`/`#[]=` (like Ruby's hashes) or to `#get`/`#set` (like
|
18
|
+
memcached or Redis).
|
19
|
+
* Compatible with the gdbm binding included in Ruby's standard library.
|
20
|
+
* Compatible with the memcached, memcache-client, memcache and
|
21
|
+
redis gems.
|
22
|
+
* Compatible with Heroku's memcached add-on.
|
23
|
+
* Compatible with Ruby 1.8.7 & 1.9
|
24
|
+
|
25
|
+
Examples
|
26
|
+
--------
|
27
|
+
|
28
|
+
### Enforcing a minimum 3-second interval between calls
|
29
|
+
|
30
|
+
Chokepoint::Interval('my block', :min => 3.0).throttle do
|
31
|
+
...
|
32
|
+
end
|
33
|
+
|
34
|
+
### Allowing a maximum of 60 requests per minute
|
35
|
+
|
36
|
+
Chokepoint::Minute('my block', :max => 60).throttle do
|
37
|
+
...
|
38
|
+
end
|
39
|
+
|
40
|
+
### Storing the rate-limiting counters on a Memcached server
|
41
|
+
|
42
|
+
require 'memcached'
|
43
|
+
|
44
|
+
Checkpoint::Interval.new('my block', :cache => Memcached.new, :key_prefix => :throttle) do
|
45
|
+
...
|
46
|
+
end
|
47
|
+
|
48
|
+
Throttling Strategies
|
49
|
+
---------------------
|
50
|
+
|
51
|
+
Chokepoint supports four built-in throttling strategies:
|
52
|
+
|
53
|
+
* `Chokepoint::Interval`: Throttles the application by enforcing a
|
54
|
+
minimum interval (by default, 1 second) between calls.
|
55
|
+
* `Chokepoint::Minute`: Throttles the application by defining a
|
56
|
+
maximum number of allowed calls per minute (by default, 60).
|
57
|
+
* `Chokepoint::Hourly`: Throttles the application by defining a
|
58
|
+
maximum number of allowed calls per hour (by default, 3,600).
|
59
|
+
* `Chokepoint::Daily`: Throttles the application by defining a
|
60
|
+
maximum number of allowed calls per day (by default, 86,400).
|
61
|
+
|
62
|
+
You can fully customize the implementation details of any of these strategies
|
63
|
+
by simply subclassing one of the aforementioned default implementations.
|
64
|
+
And, of course, should your application-specific requirements be
|
65
|
+
significantly more complex than what we've provided for, you can also define
|
66
|
+
entirely new kinds of throttling strategies by subclassing the
|
67
|
+
`Chokepoint::Limiter` base class directly.
|
68
|
+
|
69
|
+
Authors
|
70
|
+
-------
|
71
|
+
|
72
|
+
* [Matt Griffin](mailto:matt@griffinonline.org)
|
73
|
+
|
74
|
+
Based on [rack-throttle] (Public Domain) work by
|
75
|
+
[Arto Bendiken](mailto:arto.bendiken@gmail.com) and [Brendon Murphy](mailto:disposable.20.xternal@spamourmet.com>)
|
76
|
+
|
77
|
+
|
78
|
+
License
|
79
|
+
-------
|
80
|
+
|
81
|
+
Distribution is allowed under the MIT License.
|
data/lib/chokepoint.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
autoload :Limiter, 'chokepoint/limiter'
|
3
|
+
autoload :Daily, 'chokepoint/daily'
|
4
|
+
autoload :Hourly, 'chokepoint/hourly'
|
5
|
+
autoload :Minute, 'chokepoint/minute'
|
6
|
+
autoload :Interval, 'chokepoint/interval'
|
7
|
+
autoload :TimeWindow, 'chokepoint/time_window'
|
8
|
+
autoload :VERSION, 'chokepoint/version'
|
9
|
+
|
10
|
+
class RateLimitExceeded < RuntimeError; end
|
11
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
# This rate limiter strategy throttles the block by defining a
|
4
|
+
# maximum number of allowed calls per day (by default, 86,400
|
5
|
+
# requests per 24 hours, which works out to an average of 1 request per
|
6
|
+
# second).
|
7
|
+
#
|
8
|
+
# Note that this strategy doesn't use a sliding time window, but rather
|
9
|
+
# tracks requests per calendar day. This means that the throttling counter
|
10
|
+
# is reset at midnight (according to the local timezone) every
|
11
|
+
# night.
|
12
|
+
#
|
13
|
+
# @example Allowing up to 86,400 requests per day
|
14
|
+
# Chokepoint::Daily.new('activity').throttle do ... end
|
15
|
+
#
|
16
|
+
# @example Allowing up to 1,000 requests per day
|
17
|
+
# Chokepoint::Daily.new('activity', :max => 1000).throttle do ... end
|
18
|
+
#
|
19
|
+
class Daily < TimeWindow
|
20
|
+
##
|
21
|
+
# @param [String] name
|
22
|
+
# @param [Hash{Symbol => Object}] options
|
23
|
+
# @option options [Integer] :max (86400)
|
24
|
+
def initialize(name, options = {})
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
def max_per_day
|
30
|
+
@max_per_hour ||= options[:max] || 86_400
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :max_per_window, :max_per_day
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
##
|
38
|
+
# @param [Object] context
|
39
|
+
# @return [String]
|
40
|
+
def cache_key(context)
|
41
|
+
[super, Time.now.strftime('%Y-%m-%d')].join(':')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
# This rate limiter strategy throttles the block by defining a
|
4
|
+
# maximum number of allowed calls per hour (by default, 3,600
|
5
|
+
# requests per 60 minutes, which works out to an average of 1 request per
|
6
|
+
# second).
|
7
|
+
#
|
8
|
+
# Note that this strategy doesn't use a sliding time window, but rather
|
9
|
+
# tracks calls per distinct hour. This means that the throttling
|
10
|
+
# counter is reset every hour on the hour (according to the local
|
11
|
+
# timezone).
|
12
|
+
#
|
13
|
+
# @example Allowing up to 3,600 requests per hour
|
14
|
+
# Chokepoint::Hourly.new('activity').throttle do ... end
|
15
|
+
#
|
16
|
+
# @example Allowing up to 100 requests per hour
|
17
|
+
# Chokepoint::Hourly.new('activity', :max => 100).throttle do ... end
|
18
|
+
#
|
19
|
+
class Hourly < TimeWindow
|
20
|
+
##
|
21
|
+
# @param [String] name
|
22
|
+
# @param [Hash{Symbol => Object}] options
|
23
|
+
# @option options [Integer] :max (3600)
|
24
|
+
def initialize(name, options = {})
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
def max_per_hour
|
30
|
+
@max_per_hour ||= options[:max] || 3_600
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :max_per_window, :max_per_hour
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
##
|
38
|
+
# @param [Object] context
|
39
|
+
# @return [String]
|
40
|
+
def cache_key(context)
|
41
|
+
[super, Time.now.strftime('%Y-%m-%dT%H')].join(':')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
# This rate limiter strategy throttles the block by enforcing a
|
4
|
+
# minimum interval (by default, 1 second) between subsequent allowed calls.
|
5
|
+
#
|
6
|
+
# @example Allowing up to two requests per second
|
7
|
+
# Chokepoint::Interval.new('activity', :min => 0.5) # 500 ms interval
|
8
|
+
#
|
9
|
+
# @example Allowing a request every two seconds
|
10
|
+
# Chokepoint::Interval.new('activity', :min => 2) # 2000 ms interval
|
11
|
+
#
|
12
|
+
class Interval < Limiter
|
13
|
+
##
|
14
|
+
# @param [String] name
|
15
|
+
# @param [Hash{Symbol => Object}] options
|
16
|
+
# @option options [Float] :min (1.0)
|
17
|
+
def initialize(name, options = {})
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Returns `true` if sufficient time (equal to or more than
|
23
|
+
# {#minimum_interval}) has passed since the last call.
|
24
|
+
#
|
25
|
+
# @param [Object] context
|
26
|
+
# @return [Boolean]
|
27
|
+
def allowed?(context)
|
28
|
+
time_now = Time.now.to_f
|
29
|
+
last_call_time = cache_get(key = cache_key(context)) rescue nil
|
30
|
+
allowed = !last_call_time || (dt = time_now - last_call_time.to_f) >= minimum_interval
|
31
|
+
begin
|
32
|
+
cache_set(key, time_now)
|
33
|
+
allowed
|
34
|
+
rescue => e
|
35
|
+
# If an error occurred while trying to update the timestamp stored
|
36
|
+
# in the cache, we will fall back to allowing the request through.
|
37
|
+
# This prevents the application blowing up merely due to a
|
38
|
+
# backend cache server (Memcached, Redis, etc.) being offline.
|
39
|
+
allowed = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Returns the required minimal interval (in terms of seconds) that must
|
45
|
+
# elapse between two subsequent calls.
|
46
|
+
#
|
47
|
+
# @return [Float]
|
48
|
+
def minimum_interval
|
49
|
+
@min ||= (@options[:min] || 1.0).to_f
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
# This is the base class for rate limiter implementations.
|
4
|
+
#
|
5
|
+
# @example Defining a rate limiter subclass
|
6
|
+
# class MyLimiter < Limiter
|
7
|
+
# def allowed?(request)
|
8
|
+
# # TODO: custom logic goes here
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
class Limiter
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
##
|
17
|
+
# @param [String] name
|
18
|
+
# @param [Hash{Symbol => Object}] options
|
19
|
+
# @option options [String] :cache (Hash.new)
|
20
|
+
# @option options [String] :key_prefix (nil)
|
21
|
+
# @option options [Integer] :code (403)
|
22
|
+
# @option options [String] :message ("Rate Limit Exceeded")
|
23
|
+
def initialize(name, options = {})
|
24
|
+
@name, @options = name, options
|
25
|
+
end
|
26
|
+
|
27
|
+
def throttle(context = nil)
|
28
|
+
allowed?(context) ? yield : rate_limit_exceeded(context)
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns `false` if the rate limit has been exceeded for the given
|
33
|
+
# `request`, or `true` otherwise.
|
34
|
+
#
|
35
|
+
# Override this method in subclasses that implement custom rate limiter
|
36
|
+
# strategies.
|
37
|
+
#
|
38
|
+
# @param [Object] context
|
39
|
+
# @return [Boolean]
|
40
|
+
def allowed?(context = nil)
|
41
|
+
case
|
42
|
+
when whitelisted?(context) then true
|
43
|
+
when blacklisted?(context) then false
|
44
|
+
else true # override in subclasses
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Returns `true` if the originator of the given `request` is whitelisted
|
50
|
+
# (not subject to further rate limits).
|
51
|
+
#
|
52
|
+
# The default implementation always returns `false`. Override this
|
53
|
+
# method in a subclass to implement custom whitelisting logic.
|
54
|
+
#
|
55
|
+
# @param [Object] context
|
56
|
+
# @return [Boolean]
|
57
|
+
# @abstract
|
58
|
+
def whitelisted?(context)
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Returns `true` if the originator of the given `request` is blacklisted
|
64
|
+
# (not honoring rate limits, and thus permanently forbidden access
|
65
|
+
# without the need to maintain further rate limit counters).
|
66
|
+
#
|
67
|
+
# The default implementation always returns `false`. Override this
|
68
|
+
# method in a subclass to implement custom blacklisting logic.
|
69
|
+
#
|
70
|
+
# @param [Object] context
|
71
|
+
# @return [Boolean]
|
72
|
+
# @abstract
|
73
|
+
def blacklisted?(context)
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
##
|
80
|
+
# @return [Hash]
|
81
|
+
def cache
|
82
|
+
case cache = (options[:cache] ||= {})
|
83
|
+
when Proc then cache.call
|
84
|
+
else cache
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# @param [String] key
|
90
|
+
def cache_has?(key)
|
91
|
+
case
|
92
|
+
when cache.respond_to?(:has_key?)
|
93
|
+
cache.has_key?(key)
|
94
|
+
when cache.respond_to?(:get)
|
95
|
+
cache.get(key) rescue false
|
96
|
+
else false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# @param [String] key
|
102
|
+
# @return [Object]
|
103
|
+
def cache_get(key, default = nil)
|
104
|
+
case
|
105
|
+
when cache.respond_to?(:[])
|
106
|
+
cache[key] || default
|
107
|
+
when cache.respond_to?(:get)
|
108
|
+
cache.get(key) || default
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# @param [String] key
|
114
|
+
# @param [Object] value
|
115
|
+
# @return [void]
|
116
|
+
def cache_set(key, value)
|
117
|
+
case
|
118
|
+
when cache.respond_to?(:[]=)
|
119
|
+
begin
|
120
|
+
cache[key] = value
|
121
|
+
rescue TypeError => e
|
122
|
+
# GDBM throws a "TypeError: can't convert Float into String"
|
123
|
+
# exception when trying to store a Float. On the other hand, we
|
124
|
+
# don't want to unnecessarily coerce the value to a String for
|
125
|
+
# any stores that do support other data types (e.g. in-memory
|
126
|
+
# hash objects). So, this is a compromise.
|
127
|
+
cache[key] = value.to_s
|
128
|
+
end
|
129
|
+
when cache.respond_to?(:set)
|
130
|
+
cache.set(key, value)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# @param [Object] context
|
136
|
+
# @return [String]
|
137
|
+
def cache_key(context)
|
138
|
+
case
|
139
|
+
when options.has_key?(:key_prefix)
|
140
|
+
[options[:key_prefix], name].join(':')
|
141
|
+
else name
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
##
|
147
|
+
# Raise a RateLimitExceeded error
|
148
|
+
#
|
149
|
+
# @param [Object] context
|
150
|
+
# @return [void]
|
151
|
+
def rate_limit_exceeded(context)
|
152
|
+
raise RateLimitExceeded, "Rate Limit Exceeded for #{name}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
# This rate limiter strategy throttles the block by defining a
|
4
|
+
# maximum number of allowed calls per minute (by default, 60
|
5
|
+
# requests per minute, which works out to an average of 1 request per
|
6
|
+
# second).
|
7
|
+
#
|
8
|
+
# Note that this strategy doesn't use a sliding time window, but rather
|
9
|
+
# tracks calls per distinct minute. This means that the throttling
|
10
|
+
# counter is reset every minute.
|
11
|
+
#
|
12
|
+
# @example Allowing up to 60 requests/minute
|
13
|
+
# Chokepoint::Minute.new('activity').throttle do ... end
|
14
|
+
#
|
15
|
+
# @example Allowing up to 100 requests per minute
|
16
|
+
# Chokepoint::Minute.new('activity', :max => 100).throttle do ... end
|
17
|
+
#
|
18
|
+
class Minute < TimeWindow
|
19
|
+
##
|
20
|
+
# @param [String] name
|
21
|
+
# @param [Hash{Symbol => Object}] options
|
22
|
+
# @option options [Integer] :max (60)
|
23
|
+
def initialize(name, options = {})
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
def max_per_minute
|
29
|
+
@max_per_hour ||= options[:max] || 60
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :max_per_window, :max_per_minute
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param [Object] context
|
38
|
+
# @return [String]
|
39
|
+
def cache_key(context)
|
40
|
+
[super, Time.now.strftime('%Y-%m-%dT%H:%M')].join(':')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Chokepoint
|
2
|
+
##
|
3
|
+
class TimeWindow < Limiter
|
4
|
+
##
|
5
|
+
# Returns `true` if fewer than the maximum number of requests permitted
|
6
|
+
# for the current window of time have been made.
|
7
|
+
#
|
8
|
+
# @param [Object] context
|
9
|
+
# @return [Boolean]
|
10
|
+
def allowed?(context)
|
11
|
+
count = cache_get(key = cache_key(context)).to_i + 1 rescue 1
|
12
|
+
allowed = count <= max_per_window.to_i
|
13
|
+
begin
|
14
|
+
cache_set(key, count)
|
15
|
+
allowed
|
16
|
+
rescue => e
|
17
|
+
allowed = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chokepoint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matt Griffin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-02 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
version: "2.0"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: timecop
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Wrap arbitrary blocks with a chokpoint to throttle access. Chokepoints can be extended with various throttling behaviors as needed.
|
51
|
+
email: matt@griffinonline.org
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- lib/chokepoint.rb
|
60
|
+
- lib/chokepoint/daily.rb
|
61
|
+
- lib/chokepoint/hourly.rb
|
62
|
+
- lib/chokepoint/interval.rb
|
63
|
+
- lib/chokepoint/limiter.rb
|
64
|
+
- lib/chokepoint/minute.rb
|
65
|
+
- lib/chokepoint/time_window.rb
|
66
|
+
- lib/chokepoint/version.rb
|
67
|
+
- README.md
|
68
|
+
- LICENSE
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: https://github.com/Viximo/chokepoint
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 3
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.5.3
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Create chokepoints that throttle access to resources
|
103
|
+
test_files: []
|
104
|
+
|