redis-gcra 0.1.0

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: 1b80231f7f7cb343c629c565c27cf7b6d83cdef4
4
+ data.tar.gz: cdcce949d903b7da612eb466baf683fbb4479bf7
5
+ SHA512:
6
+ metadata.gz: 275086f4ec7c050d699f61c35451cb01725adfc084de2bf813de97ae1df930a2dc39fb05ba137a4c34aca2197696e728636d38dee77e800f539f565a5779eda9
7
+ data.tar.gz: 33ed550d170ccb96ba70ff1d05a3add5655ec06fa2b3ba946444c2832f1470148d11b0208d4beb89a84dbaa57ca237fa0e63c77a3202694c6f23d8e04b860d83
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Pavel Pravosud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # RedisGCRA
2
+ [![Build Status](https://travis-ci.org/rwz/redis-gcra.svg?branch=master)](https://travis-ci.org/rwz/redis-gcra)
3
+
4
+ This gem is an implementation of GCRA for rate limiting based on Redis. The
5
+ code requires Redis version 3.2+ or newer since it relies on
6
+ [`replicate_comands`][redis-replicate-commands] feature.
7
+
8
+ [redis-replicate-commands]: https://redis.io/commands/eval#replicating-commands-instead-of-scripts
9
+ ## Installation
10
+
11
+ ```ruby
12
+ gem "redis-gcra"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install redis-gcra
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ redis = Redis.new
27
+
28
+ result = RedisGCRA.limit(
29
+ redis: redis,
30
+ key: "rate-limit-key",
31
+ burst: 1000,
32
+ rate: 100,
33
+ period: 60,
34
+ cost: 2
35
+ )
36
+
37
+ result.limited? # => false - request should not be limited
38
+ result.remaning # => 998 - remaining number of requests until limited
39
+ result.retry_after # => nil - can retry without delay
40
+ result.reset_after # => ~0.6 - in 0.6s rate limiter will completely reset
41
+
42
+ # do this 500 more times and then
43
+
44
+ result.limited? # => true - request should be limited
45
+ result.remaining # => 0 - no requests can be made at this point
46
+ result.retry_after # => ~1.1 - can retry in 1.1seconds
47
+ result.reset_after # => ~600 - in 600s rate limiter will completely reset
48
+ ```
49
+
50
+ ## License
51
+
52
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
53
+
data/lib/redis-gcra.rb ADDED
@@ -0,0 +1,49 @@
1
+ require "thread"
2
+
3
+ module RedisGCRA
4
+ extend self
5
+
6
+ autoload :Result, "redis-gcra/result"
7
+
8
+ def limit(redis:, key:, burst:, rate:, period:, cost: 1)
9
+ resp = call_script(
10
+ redis,
11
+ :perform_gcra_ratelimit,
12
+ keys: [key],
13
+ argv: [burst, rate, period, cost]
14
+ )
15
+
16
+ Result.new(
17
+ limited: resp[0] == 1,
18
+ remaining: resp[1],
19
+ retry_after: resp[2] == "-1" ? nil : resp[2].to_f,
20
+ reset_after: resp[3].to_f
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def call_script(redis, script_name, *args)
27
+ script_sha = mutex.synchronize { get_cached_sha(redis, script_name) }
28
+ redis.evalsha script_sha, *args
29
+ end
30
+
31
+ def redis_cache
32
+ @redis_script_cache ||= {}
33
+ end
34
+
35
+ def mutex
36
+ @mutex ||= Mutex.new
37
+ end
38
+
39
+ def get_cached_sha(redis, script_name)
40
+ sha = redis_cache.dig(redis.id, script_name)
41
+ return sha if sha
42
+
43
+ script = File.read(File.expand_path("../../vendor/#{script_name}.lua", __FILE__))
44
+ sha = redis.script(:load, script)
45
+ redis_cache[redis.id] ||= {}
46
+ redis_cache[redis.id][script_name] = sha
47
+ sha
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ module RedisGCRA
2
+ class Result
3
+ attr_reader :remaining, :reset_after, :retry_after
4
+
5
+ def initialize(limited:, remaining:, reset_after:, retry_after:)
6
+ @limited = limited
7
+ @remaining = remaining
8
+ @reset_after = reset_after
9
+ @retry_after = retry_after
10
+ end
11
+
12
+ def limited?
13
+ !!@limited
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module RedisGCRA
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,53 @@
1
+ -- this script has side-effects, so it requires replicate commands mode
2
+ redis.replicate_commands()
3
+
4
+ local rate_limit_key = KEYS[1]
5
+ local burst = ARGV[1]
6
+ local rate = ARGV[2]
7
+ local period = ARGV[3]
8
+ local cost = ARGV[4]
9
+
10
+ local emission_interval = period / rate
11
+ local increment = emission_interval * cost
12
+ local burst_offset = emission_interval * burst
13
+ local now = redis.call("TIME")
14
+
15
+ -- redis returns time as an array containing two integers: seconds of the epoch
16
+ -- time and microseconds. for convenience we need to convert them to float
17
+ -- point number
18
+ now = now[1] + now[2] / 1000000
19
+
20
+ local tat = redis.call("GET", rate_limit_key)
21
+
22
+ if not tat then
23
+ tat = now
24
+ else
25
+ tat = tonumber(tat)
26
+ end
27
+
28
+ local new_tat = math.max(tat, now) + increment
29
+
30
+ local allow_at = new_tat - burst_offset
31
+ local diff = now - allow_at
32
+
33
+ local limited
34
+ local remaining
35
+ local retry_after
36
+ local reset_after
37
+
38
+ if diff < 0 then
39
+ limited = 1
40
+ remaining = 0
41
+ reset_after = tat - now
42
+ retry_after = diff * -1
43
+ else
44
+ local ttl = new_tat - now
45
+ redis.call("SET", rate_limit_key, new_tat, "EX", math.ceil(ttl))
46
+ local next_in = burst_offset - ttl
47
+ remaining = math.floor(next_in / emission_interval)
48
+ reset_after = ttl
49
+ retry_after = -1
50
+ limited = 0
51
+ end
52
+
53
+ return {limited, remaining, tostring(retry_after), tostring(reset_after)}
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis-gcra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pavel Pravosud
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.3'
27
+ description:
28
+ email:
29
+ - pavel@pravosud.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/redis-gcra.rb
37
+ - lib/redis-gcra/result.rb
38
+ - lib/redis-gcra/version.rb
39
+ - vendor/perform_gcra_ratelimit.lua
40
+ homepage: https://github.com/rwz/redis-gcra
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.6.8
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Rate limiting based on Generic Cell Rate Algorithm
64
+ test_files: []