glebprop 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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in glebprop.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/glebprop.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "glebprop/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "glebprop"
7
+ s.version = Glebprop::VERSION
8
+ s.authors = ["Gleb Pomykalov"]
9
+ s.email = ["gleb.pomykalov@skype.net"]
10
+ s.homepage = ""
11
+ s.summary = %q{Implement rate limiting}
12
+ s.description = %q{See gem called Prop - this one is just a copy}
13
+
14
+ s.rubyforge_project = "glebprop"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+ end
@@ -0,0 +1,3 @@
1
+ module Glebprop
2
+ VERSION = "0.0.1"
3
+ end
data/lib/glebprop.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "glebprop/version"
2
+ require 'prop'
3
+
4
+ module Glebprop
5
+ # Your code goes here...
6
+ end
data/lib/prop.rb ADDED
@@ -0,0 +1,116 @@
1
+ require 'digest/md5'
2
+
3
+ class Object
4
+ def define_prop_class_method(name, &blk)
5
+ (class << self; self; end).instance_eval { define_method(name, &blk) }
6
+ end
7
+ end
8
+
9
+ class Prop
10
+ class RateLimitExceededError < RuntimeError
11
+ attr_accessor :handle, :retry_after
12
+
13
+ def self.create(handle, key, threshold)
14
+ error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
15
+ error.handle = handle
16
+ error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
17
+ raise error
18
+ end
19
+ end
20
+
21
+ class << self
22
+ attr_accessor :handles, :reader, :writer
23
+
24
+ def read(&blk)
25
+ self.reader = blk
26
+ end
27
+
28
+ def write(&blk)
29
+ self.writer = blk
30
+ end
31
+
32
+ def increment(&blk)
33
+ self.incrementer = blk
34
+ end
35
+
36
+ def incrementer=(value)
37
+ @incrementer = value
38
+ end
39
+
40
+ def incrementer
41
+ @incrementer ? Proc.new { |key, inc| @incrementer.call(key, inc) } :
42
+ Proc.new { |key, inc| self.writer.call(key, (self.reader.call(key) || 0).to_i + inc) }
43
+ end
44
+
45
+ def defaults(handle, defaults)
46
+ raise RuntimeError.new("Invalid threshold setting") unless defaults[:threshold].to_i > 0
47
+ raise RuntimeError.new("Invalid interval setting") unless defaults[:interval].to_i > 0
48
+
49
+ self.handles ||= {}
50
+ self.handles[handle] = defaults
51
+ end
52
+
53
+ def throttle!(handle, key = nil, options = {})
54
+ options = sanitized_prop_options(handle, key, options)
55
+ cache_key = sanitized_prop_key(key, options[:interval])
56
+ counter = reader.call(cache_key).to_i
57
+
58
+ incrementer.call(cache_key, [ 1, options[:increment].to_i ].max)
59
+
60
+ if counter >= options[:threshold]
61
+ raise Prop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
62
+ end
63
+ end
64
+
65
+ def throttled?(handle, key = nil, options = {})
66
+ options = sanitized_prop_options(handle, key, options)
67
+ cache_key = sanitized_prop_key(key, options[:interval])
68
+
69
+ reader.call(cache_key).to_i >= options[:threshold]
70
+ end
71
+
72
+ def reset(handle, key = nil, options = {})
73
+ options = sanitized_prop_options(handle, key, options)
74
+ cache_key = sanitized_prop_key(key, options[:interval])
75
+
76
+ writer.call(cache_key, 0)
77
+ end
78
+
79
+ def query(handle, key = nil, options = {})
80
+ options = sanitized_prop_options(handle, key, options)
81
+ cache_key = sanitized_prop_key(key, options[:interval])
82
+
83
+ reader.call(cache_key).to_i
84
+ end
85
+
86
+ private
87
+
88
+ # Builds the expiring cache key
89
+ def sanitized_prop_key(key, interval)
90
+ window = (Time.now.to_i / interval)
91
+ cache_key = "#{normalize_cache_key(key)}/#{ window }"
92
+ "prop/#{Digest::MD5.hexdigest(cache_key)}"
93
+ end
94
+
95
+ # Sanitizes the option set and sets defaults
96
+ def sanitized_prop_options(handle, key, options)
97
+ defaults = (handles || {})[handle] || {}
98
+ return {
99
+ :key => normalize_cache_key(key),
100
+ :increment => defaults[:increment],
101
+ :threshold => defaults[:threshold].to_i,
102
+ :interval => defaults[:interval].to_i
103
+ }.merge(options)
104
+ end
105
+
106
+ # Simple key expansion only supports arrays and primitives
107
+ def normalize_cache_key(key)
108
+ if key.is_a?(Array)
109
+ key.map { |part| normalize_cache_key(part) }.join('/')
110
+ else
111
+ key.to_s
112
+ end
113
+ end
114
+
115
+ end
116
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: glebprop
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Gleb Pomykalov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-29 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: See gem called Prop - this one is just a copy
22
+ email:
23
+ - gleb.pomykalov@skype.net
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - Rakefile
34
+ - glebprop.gemspec
35
+ - lib/glebprop.rb
36
+ - lib/glebprop/version.rb
37
+ - lib/prop.rb
38
+ homepage: ""
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ hash: 3
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project: glebprop
67
+ rubygems_version: 1.8.6
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Implement rate limiting
71
+ test_files: []
72
+