galago-rate_limiter 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: 6af298c6a194e5e8d7939e3e18890698efc6c4ef
4
+ data.tar.gz: 16a8dd9c1a13dfdf993c7b1f42ac0377ab65f1a9
5
+ SHA512:
6
+ metadata.gz: 2e046b041e5376884f6d79b693c92277b4b34ccc8836d98645badf33754f2a4879b5c84ba4d0240d7ef3166da2e71255fb2846d6621bb6c2d88260fd438532a2
7
+ data.tar.gz: 69563efbb8f7b0dfae163918c642a1d321e85d280d226666f090380e4f2e46617988239a7d3764224f0361853682b336f291179e1f03753f422f628c1f791f71
@@ -0,0 +1,47 @@
1
+ require 'singleton'
2
+
3
+ module Galago
4
+ class RateLimiter
5
+ class Configuration
6
+ include Singleton
7
+
8
+ DEFAULT_LIMIT = 5_000
9
+ DEFAULT_API_KEY_HEADER = 'HTTP_X_API_KEY'.freeze
10
+
11
+ attr_reader :api_key_header, :limit, :counter
12
+
13
+ def initialize
14
+ @limit = DEFAULT_LIMIT
15
+ @api_key_header = DEFAULT_API_KEY_HEADER
16
+ end
17
+
18
+ def limit=(limit)
19
+ raise ArgumentError.new("Limit must be a positive number") if limit < 1
20
+ @limit = limit
21
+ end
22
+
23
+ def api_key_header=(api_key_header)
24
+ header = api_key_header.dup
25
+ header.gsub!('-', '_')
26
+ header.upcase!
27
+
28
+ @api_key_header = "HTTP_#{header}".freeze
29
+ end
30
+
31
+ def counter=(counter)
32
+ @counter = case counter
33
+ when Dalli::Client then MemcachedCounter.new(counter)
34
+ when Redis then RedisCounter.new(counter)
35
+ else counter
36
+ end
37
+ end
38
+
39
+ def reset!
40
+ @limit = DEFAULT_LIMIT
41
+ @api_key_header = DEFAULT_API_KEY_HEADER
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,18 @@
1
+ module Galago
2
+ class RateLimiter
3
+ class MemcachedCounter
4
+ def initialize(client)
5
+ @memcached = client
6
+ end
7
+
8
+ def increment(key, amount, options = {})
9
+ @memcached.incr(key, amount, options[:expires_in], 1)
10
+ end
11
+
12
+ def reset!
13
+ @memcached.flush
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,12 @@
1
+ module Galago
2
+ class RateLimiter::Railtie < ::Rails::Railtie
3
+ initializer "galago.rate_limiter.configure_counter" do |app|
4
+ app.config.middleware.use "Galago::RateLimiter"
5
+
6
+ RateLimiter.configure do |config|
7
+ config.counter = Rails.cache
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,22 @@
1
+ module Galago
2
+ class RateLimiter
3
+ class RedisCounter
4
+ def initialize(client)
5
+ @redis = client
6
+ end
7
+
8
+ def increment(key, amount, options = {})
9
+ count, _ = @redis.multi do |multi|
10
+ multi.incrby(key, amount)
11
+ multi.expire(key, options[:expires_in])
12
+ end
13
+ count
14
+ end
15
+
16
+ def reset!
17
+ @redis.flushdb
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,67 @@
1
+ require "json"
2
+ require_relative "./rate_limiter/configuration"
3
+ require_relative "./rate_limiter/memcached_counter"
4
+ require_relative "./rate_limiter/redis_counter"
5
+ require_relative "./rate_limiter/railtie" if defined?(Rails)
6
+
7
+ module Galago
8
+ class RateLimiter
9
+ X_LIMIT_HEADER = 'X-RateLimit-Limit'.freeze
10
+ X_RESET_HEADER = 'X-RateLimit-Reset'.freeze
11
+ X_REMAINING_HEADER = 'X-RateLimit-Remaining'.freeze
12
+
13
+ def self.configure
14
+ yield Configuration.instance
15
+ end
16
+
17
+ def initialize(app)
18
+ @app = app
19
+ @config = Configuration.instance
20
+ @counter = @config.counter
21
+ end
22
+
23
+ def call(env)
24
+ api_key = env[@config.api_key_header]
25
+ return @app.call(env) if api_key.nil?
26
+ throughput = @counter.increment(api_key, 1, expires_in: expires_in)
27
+
28
+ if limit_exceeded?(throughput)
29
+ status = 403
30
+ headers = {
31
+ X_LIMIT_HEADER => @config.limit.to_s,
32
+ X_REMAINING_HEADER => "0",
33
+ X_RESET_HEADER => limit_resets_at.to_s
34
+ }
35
+ body = [JSON(message: "API rate limit exceeded for #{api_key}")]
36
+ else
37
+ status, headers, body = @app.call(env)
38
+ headers[X_LIMIT_HEADER] = @config.limit.to_s
39
+ headers[X_REMAINING_HEADER] = (@config.limit - throughput).to_s
40
+ headers[X_RESET_HEADER] = limit_resets_at.to_s
41
+ end
42
+
43
+ [status, headers, body]
44
+ end
45
+
46
+ private
47
+
48
+ def limit_exceeded?(throughput)
49
+ throughput > @config.limit
50
+ end
51
+
52
+ def timestamp
53
+ @timestamp ||= Time.now.utc.to_i
54
+ end
55
+
56
+ def expires_in
57
+ timestamp % 3600
58
+ end
59
+
60
+ # Reset at the beginning of every hour.
61
+ def limit_resets_at
62
+ timestamp - (timestamp % 3600) + 3600
63
+ end
64
+
65
+ end
66
+ end
67
+
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: galago-rate_limiter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe Karayusuf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dalli
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email:
57
+ - jkarayusuf@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/galago/rate_limiter.rb
63
+ - lib/galago/rate_limiter/configuration.rb
64
+ - lib/galago/rate_limiter/memcached_counter.rb
65
+ - lib/galago/rate_limiter/railtie.rb
66
+ - lib/galago/rate_limiter/redis_counter.rb
67
+ homepage: ''
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.0
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: GitHub style API Rate limiter
91
+ test_files: []