comparison gpp/antispam/rate_limit.py @ 479:32cec6cd8808

Refactor RateLimiter so that if Redis is not running, everything still runs normally (minus the rate limiting protection). My assumption that creating a Redis connection would throw an exception if Redis wasn't running was wrong. The exceptions actually occur when you issue a command. This is for #224.
author Brian Neal <bgneal@gmail.com>
date Sun, 25 Sep 2011 00:49:05 +0000
parents 5e826e232932
children 6f5fff924877
comparison
equal deleted inserted replaced
478:d280b27fed17 479:32cec6cd8808
15 HOST = getattr(settings, 'RATE_LIMIT_REDIS_HOST', 'localhost') 15 HOST = getattr(settings, 'RATE_LIMIT_REDIS_HOST', 'localhost')
16 PORT = getattr(settings, 'RATE_LIMIT_REDIS_PORT', 6379) 16 PORT = getattr(settings, 'RATE_LIMIT_REDIS_PORT', 6379)
17 DB = getattr(settings, 'RATE_LIMIT_REDIS_DB', 0) 17 DB = getattr(settings, 'RATE_LIMIT_REDIS_DB', 0)
18 18
19 19
20 # This exception is thrown upon any Redis error. This insulates client code from
21 # knowing that we are using Redis and will allow us to use something else in the
22 # future.
20 class RateLimiterUnavailable(Exception): 23 class RateLimiterUnavailable(Exception):
21 pass 24 pass
22 25
23 26
24 def _make_key(ip): 27 def _make_key(ip):
60 63
61 """ 64 """
62 key = _make_key(ip) 65 key = _make_key(ip)
63 conn = _get_connection() 66 conn = _get_connection()
64 67
65 conn.setex(key, count, _to_seconds(interval)) 68 try:
69 conn.setex(key, count, _to_seconds(interval))
70 except redis.RedisError, e:
71 logger.error("rate limit (block_ip): %s" % e)
72 raise RateLimiterUnavailable
73
66 logger.info("Rate limiter blocked IP %s; %d / %s", ip, count, interval) 74 logger.info("Rate limiter blocked IP %s; %d / %s", ip, count, interval)
67 75
68 76
69 class RateLimiter(object): 77 class RateLimiter(object):
70 """ 78 """
82 def is_blocked(self): 90 def is_blocked(self):
83 """ 91 """
84 Return True if the IP is blocked, and false otherwise. 92 Return True if the IP is blocked, and false otherwise.
85 93
86 """ 94 """
87 val = self.conn.get(self.key) 95 try:
96 val = self.conn.get(self.key)
97 except redis.RedisError, e:
98 logger.error("RateLimiter (is_blocked): %s" % e)
99 raise RateLimiterUnavailable
100
88 try: 101 try:
89 val = int(val) if val else 0 102 val = int(val) if val else 0
90 except ValueError: 103 except ValueError:
91 return False 104 return False
92 105
103 otherwise. If the set_point is exceeded for the first time, the counter 116 otherwise. If the set_point is exceeded for the first time, the counter
104 associated with the IP is set to expire according to the lockout 117 associated with the IP is set to expire according to the lockout
105 parameter. 118 parameter.
106 119
107 """ 120 """
108 val = self.conn.incr(self.key) 121 try:
122 val = self.conn.incr(self.key)
109 123
110 # Set expire time, if necessary. 124 # Set expire time, if necessary.
111 # If this is the first time, set it according to interval. 125 # If this is the first time, set it according to interval.
112 # If the set_point has just been exceeded, set it according to lockout. 126 # If the set_point has just been exceeded, set it according to lockout.
113 if val == 1: 127 if val == 1:
114 self.conn.expire(self.key, _to_seconds(self.interval)) 128 self.conn.expire(self.key, _to_seconds(self.interval))
115 elif val == self.set_point: 129 elif val == self.set_point:
116 self.conn.expire(self.key, _to_seconds(self.lockout)) 130 self.conn.expire(self.key, _to_seconds(self.lockout))
117 131
118 tripped = val >= self.set_point 132 tripped = val >= self.set_point
119 133
120 if tripped: 134 if tripped:
121 logger.info("Rate limiter tripped for %s; counter = %d", self.ip, val) 135 logger.info("Rate limiter tripped for %s; counter = %d", self.ip, val)
136 return tripped
122 137
123 return tripped 138 except redis.RedisError, e:
139 logger.error("RateLimiter (incr): %s" % e)
140 raise RateLimiterUnavailable