get(get_class(), 'lock_bypage')) { $key .= '_' . md5($itemkey); } // Add user-specific identifier if(Config::inst()->get(get_class(), 'lock_byuserip') && Controller::has_curr()) { $ip = Controller::curr()->getRequest()->getIP(); $key .= '_' . md5($ip); } return $key; } public function getContent($key, $callback) { // Bypass rate limiting if flushing, or timeout isn't set $timeout = Config::inst()->get(get_class(), 'lock_timeout'); if(isset($_GET['flush']) || !$timeout) { return parent::getContent($key, $callback); } // Generate result with rate limiting enabled $limitKey = $this->getCacheKey($key); $cache = $this->getCache(); if($lockedUntil = $cache->load($limitKey)) { if(time() < $lockedUntil) { // Politely inform visitor of limit $response = new HTTPResponse_Exception('Too Many Requests.', 429); $response->getResponse()->addHeader('Retry-After', 1 + $lockedUntil - time()); throw $response; } } // Apply rate limit $cache->save(time() + $timeout, $limitKey); // Generate results $result = parent::getContent($key, $callback); // Reset rate limit with optional cooldown if($cooldown = Config::inst()->get(get_class(), 'lock_cooldown')) { // Set cooldown on successful query execution $cache->save(time() + $cooldown, $limitKey); } else { // Without cooldown simply disable lock $cache->remove($limitKey); } return $result; } }