changeset 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 d280b27fed17
children 1a7ca5fa494f
files gpp/antispam/decorators.py gpp/antispam/rate_limit.py
diffstat 2 files changed, 40 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- 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
 
--- 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