# HG changeset patch # User Brian Neal # Date 1316911745 0 # Node ID 32cec6cd8808e216161f9d35e6041966d67d4986 # Parent d280b27fed17e56cc2fe610133dbb086278bb638 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. diff -r d280b27fed17 -r 32cec6cd8808 gpp/antispam/decorators.py --- a/gpp/antispam/decorators.py Sun Sep 11 19:50:59 2011 +0000 +++ b/gpp/antispam/decorators.py Sun Sep 25 00:49:05 2011 +0000 @@ -21,20 +21,24 @@ ip = request.META.get('REMOTE_ADDR') try: rate_limiter = RateLimiter(ip, count, interval, lockout) + if rate_limiter.is_blocked(): + return render(request, 'antispam/blocked.html', status=403) + except RateLimiterUnavailable: # just call the function and return the result return fn(request, *args, **kwargs) - if rate_limiter.is_blocked(): - return render(request, 'antispam/blocked.html', status=403) - response = fn(request, *args, **kwargs) if request.method == 'POST': success = (response and response.has_header('location') and response.status_code == 302) - if not success and rate_limiter.incr(): - return render(request, 'antispam/blocked.html', status=403) + try: + if not success and rate_limiter.incr(): + return render(request, 'antispam/blocked.html', status=403) + + except RateLimiterUnavailable: + pass return response diff -r d280b27fed17 -r 32cec6cd8808 gpp/antispam/rate_limit.py --- a/gpp/antispam/rate_limit.py Sun Sep 11 19:50:59 2011 +0000 +++ b/gpp/antispam/rate_limit.py Sun Sep 25 00:49:05 2011 +0000 @@ -17,6 +17,9 @@ DB = getattr(settings, 'RATE_LIMIT_REDIS_DB', 0) +# This exception is thrown upon any Redis error. This insulates client code from +# knowing that we are using Redis and will allow us to use something else in the +# future. class RateLimiterUnavailable(Exception): pass @@ -62,7 +65,12 @@ key = _make_key(ip) conn = _get_connection() - conn.setex(key, count, _to_seconds(interval)) + try: + conn.setex(key, count, _to_seconds(interval)) + except redis.RedisError, e: + logger.error("rate limit (block_ip): %s" % e) + raise RateLimiterUnavailable + logger.info("Rate limiter blocked IP %s; %d / %s", ip, count, interval) @@ -84,7 +92,12 @@ Return True if the IP is blocked, and false otherwise. """ - val = self.conn.get(self.key) + try: + val = self.conn.get(self.key) + except redis.RedisError, e: + logger.error("RateLimiter (is_blocked): %s" % e) + raise RateLimiterUnavailable + try: val = int(val) if val else 0 except ValueError: @@ -105,19 +118,23 @@ parameter. """ - val = self.conn.incr(self.key) + try: + val = self.conn.incr(self.key) - # Set expire time, if necessary. - # If this is the first time, set it according to interval. - # If the set_point has just been exceeded, set it according to lockout. - if val == 1: - self.conn.expire(self.key, _to_seconds(self.interval)) - elif val == self.set_point: - self.conn.expire(self.key, _to_seconds(self.lockout)) + # Set expire time, if necessary. + # If this is the first time, set it according to interval. + # If the set_point has just been exceeded, set it according to lockout. + if val == 1: + self.conn.expire(self.key, _to_seconds(self.interval)) + elif val == self.set_point: + self.conn.expire(self.key, _to_seconds(self.lockout)) - tripped = val >= self.set_point + tripped = val >= self.set_point - if tripped: - logger.info("Rate limiter tripped for %s; counter = %d", self.ip, val) + if tripped: + logger.info("Rate limiter tripped for %s; counter = %d", self.ip, val) + return tripped - return tripped + except redis.RedisError, e: + logger.error("RateLimiter (incr): %s" % e) + raise RateLimiterUnavailable