sinatra-rate-limiter 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/lib/sinatra/rate-limiter.rb +63 -71
  4. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2eb98d3fb4708f2dde94dc7aa8fac5e790eea55
4
- data.tar.gz: eff8f03476da900eff856f94b20989608cd07c1d
3
+ metadata.gz: 3442a4980f8b129023849abb927d12628d3264a2
4
+ data.tar.gz: f2a4160d6df1e036eb16d214fff7a05c6fa23705
5
5
  SHA512:
6
- metadata.gz: cc4c520d384178af38c646dd55aedfcf08ee89d96a464825b4c26f3ddc66e3084e9a0a2831d2e68772073c75dd583b97636acb9a29b0fd96aec670ba2c22661b
7
- data.tar.gz: 409c06eefea02caf1cea1c6a7b8a5fa662abaa7647a11660011c6a30e2c5ff2fada3e04e8f565a6e5f75e3e5452353922476b1bf4d2c4e1e2b568d07a996cb26
6
+ metadata.gz: 014e4148653366b3bb5bdfd8bfe42f378688c13803dbeacfa80a5ba88b9485377f2cb9fe1da7f68c9f799c40411ca43822db32b370ef48971ba62a7c3e91e3bb
7
+ data.tar.gz: ba1a7361fc46fe41d97ca057ffce1ac547ad49d456ea8b25760dd49a2baeed9b747b65a61c7b8d71e332020a66e66194978202298821f9c16fa2a2ed5de5d201
data/README.md CHANGED
@@ -52,7 +52,7 @@ Use `rate_limit` in the pipeline of any route (i.e. in the route itself, or
52
52
  in a `before` filter, or in a Padrino controller, etc. `rate_limit` takes
53
53
  zero to infinite parameters, with the syntax:
54
54
 
55
- ```
55
+ ```ruby
56
56
  rate_limit [BucketName], [[<Requests>, <Seconds>], ...], [[<Key>: <Value>], ...]
57
57
  ```
58
58
 
@@ -67,11 +67,10 @@ See the _Examples_ section below for usage examples.
67
67
 
68
68
  When a rate limit is exceeded, the exception `Sinatra::RateLimiter::Exceeded`
69
69
  is thrown. By default, this sends an response code `429` with a simple plain
70
- text error message. You can use Sinatra's error handling to customise this.
71
-
72
- E.g.:
70
+ text error message. You can use Sinatra's error handling to customise this,
71
+ for example to provide a JSON response with status code 400:
73
72
 
74
- ```
73
+ ```ruby
75
74
  error Sinatra::RateLimiter::Exceeded do
76
75
  status 400
77
76
  content_type :json
@@ -82,11 +81,12 @@ E.g.:
82
81
 
83
82
  As well as the default error message being available in
84
83
  `env['sinatra.error'].message`, `the env['sinatra.error.rate_limiter']`
85
- object contains three values for the exceeded limit:
84
+ object contains four values for the exceeded limit:
86
85
 
87
86
  * `.requests` Integer of the number of requests allowed
88
87
  * `.seconds` Integer of the number of seconds the request limit applies to
89
88
  * `.try_again` Integer the number of seconds until the limit resets
89
+ * `.bucket` Name of the triggering bucket
90
90
 
91
91
  ## Configuration
92
92
 
@@ -56,7 +56,7 @@ module Sinatra
56
56
  when :header_prefix
57
57
  raise ArgumentError, 'header_prefix must be a String' if value.class != String
58
58
  when :identifier
59
- raise ArgumentError, 'identifier must be a Proc or nil' if (!value.nil? and value.class != Proc)
59
+ raise ArgumentError, 'identifier must be a Proc' if value.class != Proc
60
60
  else
61
61
  raise ArgumentError, "Invalid option #{option}"
62
62
  end
@@ -96,99 +96,91 @@ module Sinatra
96
96
  end
97
97
  end
98
98
 
99
- end
99
+ class RateLimit
100
+ attr_accessor :settings, :request, :options
100
101
 
101
- class RateLimit
102
- attr_accessor :settings, :request, :options
102
+ def initialize(bucket, limits)
103
+ @bucket = bucket
104
+ @limits = limits
105
+ @time_prefix = get_min_time_prefix(@limits)
106
+ end
103
107
 
104
- def initialize(bucket, limits)
105
- @bucket = bucket
106
- @limits = limits
107
- @time_prefix = get_min_time_prefix(@limits)
108
- end
108
+ def options=(options)
109
+ options = settings.rate_limiter_default_options.merge(options)
110
+ @options = Struct.new(*options.keys).new(*options.values)
111
+ end
109
112
 
110
- def options=(options)
111
- options = settings.rate_limiter_default_options.merge(options)
112
- @options = Struct.new(*options.keys).new(*options.values)
113
- end
113
+ def identifier
114
+ @identifier ||= @options.identifier.call(request)
115
+ end
114
116
 
115
- def history(seconds=0)
116
- redis_history.select{|t| seconds.eql?(0) ? true : t > (Time.now.to_f - seconds)}
117
- end
117
+ def history(seconds=0)
118
+ redis_history.select{|t| seconds.eql?(0) ? true : t > (Time.now.to_f - seconds)}
119
+ end
120
+
121
+ def headers
122
+ headers = []
118
123
 
119
- def headers
120
- headers = []
124
+ header_prefix = @options.header_prefix + (@bucket.eql?('default') ? '' : '-' + @bucket)
125
+ limit_no = 0 if @limits.length > 1
126
+ @limits.each do |limit|
127
+ limit_no = limit_no + 1 if limit_no
128
+ headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Limit', limit[:requests]]
129
+ headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Remaining', limit_remaining(limit)]
130
+ headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Reset', limit_reset(limit)]
131
+ end
121
132
 
122
- header_prefix = @options.header_prefix + (@bucket.eql?('default') ? '' : '-' + @bucket)
123
- limit_no = 0 if @limits.length > 1
124
- @limits.each do |limit|
125
- limit_no = limit_no + 1 if limit_no
126
- headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Limit', limit[:requests]]
127
- headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Remaining', limit_remaining(limit)]
128
- headers << [header_prefix + (limit_no ? "-#{limit_no}" : '') + '-Reset', limit_reset(limit)]
133
+ return headers
129
134
  end
130
135
 
131
- return headers
132
- end
136
+ def limit_remaining(limit)
137
+ limit[:requests] - history(limit[:seconds]).length
138
+ end
133
139
 
134
- def limit_remaining(limit)
135
- limit[:requests] - history(limit[:seconds]).length
136
- end
140
+ def limit_reset(limit)
141
+ limit[:seconds] - (Time.now.to_f - history(limit[:seconds]).first.to_f).to_i
142
+ end
137
143
 
138
- def limit_reset(limit)
139
- limit[:seconds] - (Time.now.to_f - history(limit[:seconds]).first.to_f).to_i
140
- end
144
+ def limits_exceeded?
145
+ exceeded = @limits.select {|limit| limit_remaining(limit) < 1}.sort_by{|e| e[:seconds]}.last
141
146
 
142
- def limits_exceeded?
143
- exceeded = @limits.select {|limit| limit_remaining(limit) < 1}.sort_by{|e| e[:seconds]}.last
147
+ if exceeded
148
+ try_again = limit_reset(exceeded)
149
+ return exceeded.merge({try_again: try_again.to_i, bucket: @bucket})
150
+ end
151
+ end
144
152
 
145
- if exceeded
146
- try_again = limit_reset(exceeded)
147
- return exceeded.merge({try_again: try_again.to_i, bucket: @bucket})
153
+ def log_request
154
+ redis.setex(
155
+ [namespace, identifier, @bucket, Time.now.to_f.to_s].join('/'),
156
+ @settings.rate_limiter_redis_expires,
157
+ nil)
148
158
  end
149
- end
150
159
 
151
- def log_request
152
- redis.setex(
153
- [namespace, user_identifier, @bucket, Time.now.to_f.to_s].join('/'),
154
- @settings.rate_limiter_redis_expires,
155
- nil)
156
- end
160
+ private
157
161
 
158
- private
162
+ def redis_history
163
+ @history ||= redis.
164
+ keys("#{[namespace,identifier,@bucket].join('/')}/#{@time_prefix}*").
165
+ map{|k| k.split('/')[3].to_f}
166
+ end
159
167
 
160
- def redis_history
161
- if @history
162
- @history
163
- else
164
- @history = redis.
165
- keys("#{[namespace,user_identifier,@bucket].join('/')}/#{@time_prefix}*").
166
- map{|k| k.split('/')[3].to_f}
168
+ def redis
169
+ @settings.rate_limiter_redis_conn
167
170
  end
168
- end
169
171
 
170
- def user_identifier
171
- if @options.identifier.class == Proc
172
- return @options.identifier.call(request)
173
- else
174
- return request.ip
172
+ def namespace
173
+ @settings.rate_limiter_redis_namespace
175
174
  end
176
- end
177
175
 
178
- def redis
179
- @settings.rate_limiter_redis_conn
180
- end
176
+ def get_min_time_prefix(limits)
177
+ now = Time.now.to_f
178
+ oldest = Time.now.to_f - limits.sort_by{|l| -l[:seconds]}.first[:seconds]
181
179
 
182
- def namespace
183
- @settings.rate_limiter_redis_namespace
180
+ return now.to_s[0..((now/oldest).to_s.split(/^1\.|[1-9]+/)[1].length)].to_i.to_s
181
+ end
184
182
  end
185
183
 
186
- def get_min_time_prefix(limits)
187
- now = Time.now.to_f
188
- oldest = Time.now.to_f - limits.sort_by{|l| -l[:seconds]}.first[:seconds]
189
-
190
- return now.to_s[0..((now/oldest).to_s.split(/^1\.|[1-9]+/)[1].length)].to_i.to_s
191
- end
192
184
  end
193
185
 
194
186
  register RateLimiter
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-rate-limiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Warren Guy