Merge pull request #1906 from basilfx/ratelimit

Initial version of rate limiting on requests
This commit is contained in:
AdeHub
2014-09-22 10:16:26 +12:00
3 changed files with 56 additions and 9 deletions

View File

@@ -38,7 +38,7 @@
%>
<tr class="grade${grade}">
%if type == 'album':
<td id="albumart" style=" text-align: center; vertical-align: middle;"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50" onerror="this.onerror=null;this.src='${caa_group_url}'"></div></td>
<td id="albumart" style=" text-align: center; vertical-align: middle;"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50" onerror="tryCCA(this, '${caa_group_url}')"></div></td>
%else:
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
%endif
@@ -72,7 +72,13 @@
<script src="js/libs/jquery.dataTables.min.js"></script>
<script type="text/javascript">
function tryCCA(element, url) {
element.onerror = function() {
element.onerror = null;
element.src = "interfaces/default/images/no-cover-art.png";
};
element.src = url;
}
function getArt() {
$("table#searchresults_table tr td#albumart img").each(function(){
var id = $(this).attr('title');
@@ -85,7 +91,6 @@
});
}
function initThisPage() {
getArt();
$('#searchresults_table').dataTable({
"bDestroy": true,
"aoColumnDefs": [
@@ -103,8 +108,10 @@
"aaSorting": []
});
$('#searchresults_table').on("draw.dt", function () {
getArt();
$("img").unveil();
});
getArt();
resetFilters("album");
}
$(document).ready(function(){

View File

@@ -13,18 +13,23 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
import random
import time
import random
import threading
import headphones
from headphones import db, logger, request
from collections import defaultdict
TIMEOUT = 60 # seconds
TIMEOUT = 60.0 # seconds
REQUEST_LIMIT = 1.0 # seconds
ENTRY_POINT = "http://ws.audioscrobbler.com/2.0/"
API_KEY = "395e6ec6bb557382fc41fde867bce66f"
# Required for API request limit
lock = threading.Lock()
def request_lastfm(method, **kwargs):
"""
Call a Last.FM API method. Automatically sets the method and API key. Method
@@ -43,7 +48,8 @@ def request_lastfm(method, **kwargs):
logger.debug("Calling Last.FM method: %s", method)
logger.debug("Last.FM call parameters: %s", kwargs)
data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs)
data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs,
rate_limit=(lock, REQUEST_LIMIT))
# Parse response and check for errors.
if not data:

View File

@@ -18,16 +18,27 @@ from headphones import logger
from xml.dom import minidom
from bs4 import BeautifulSoup
import time
import requests
import feedparser
import headphones
import collections
# Dictionary with last request times, for rate limiting.
last_requests = collections.defaultdict(int)
def request_response(url, method="get", auto_raise=True,
whitelist_status_code=None, **kwargs):
whitelist_status_code=None, rate_limit=None, **kwargs):
"""
Convenient wrapper for `requests.get', which will capture the exceptions and
log them. On success, the Response object is returned. In case of a
exception, None is returned.
Additionally, there is support for rate limiting. To use this feature,
supply a tuple of (lock, request_limit). The lock is used to make sure no
other request with the same lock is executed. The request limit is the
minimal time between two requests (and so 1/request_limit is the number of
requests per seconds).
"""
# Convert whitelist_status_code to a list if needed
@@ -43,9 +54,32 @@ def request_response(url, method="get", auto_raise=True,
request_method = getattr(requests, method.lower())
try:
# Request the URL
# Enfore request rate limit if applicable. This uses the lock so there
# is synchronized access to the API. If no limit is enforced, just do
# it as usual.
logger.debug("Requesting URL via %s method: %s", method.upper(), url)
response = request_method(url, **kwargs)
if rate_limit:
lock, request_limit = rate_limit
with lock:
delta = time.time() - last_requests[lock]
limit = int(1.0 / request_limit)
if delta < request_limit:
logger.debug("Sleeping %.2f seconds for request, limit " \
"is %d req/sec.", request_limit - delta, limit)
# Sleep the remaining time
time.sleep(request_limit - delta)
# Update last request time and start with request. Basically, if
# the request takes N seconds, next request will sleep N seconds
# less.
last_requests[lock] = time.time()
response = request_method(url, **kwargs)
else:
response = request_method(url, **kwargs)
# If status code != OK, then raise exception, except if the status code
# is white listed.