misc: add a cache duration option to webservice calls (#51359) #567
|
@ -295,3 +295,34 @@ def test_webservice_timeout(http_requests, pub):
|
|||
except Exception:
|
||||
pass
|
||||
assert http_requests.get_last('timeout') == 10
|
||||
|
||||
|
||||
def test_webservice_cache(http_requests, pub):
|
||||
NamedWsCall.wipe()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello world'
|
||||
wscall.request = {
|
||||
'method': 'GET',
|
||||
'url': 'http://remote.example.net/json',
|
||||
'cache_duration': '120',
|
||||
}
|
||||
wscall.store()
|
||||
wscall = NamedWsCall.get(wscall.id)
|
||||
assert wscall.request['cache_duration'] == '120'
|
||||
assert wscall.call() == {'foo': 'bar'}
|
||||
assert http_requests.count() == 1
|
||||
# remove request cache
|
||||
pub.get_request().wscalls_cache = {}
|
||||
assert wscall.call() == {'foo': 'bar'}
|
||||
assert http_requests.count() == 1
|
||||
|
||||
# make request without cache
|
||||
wscall.request = {
|
||||
'method': 'GET',
|
||||
'url': 'http://remote.example.net/json',
|
||||
'cache_duration': None,
|
||||
}
|
||||
pub.get_request().wscalls_cache = {}
|
||||
assert wscall.call() == {'foo': 'bar'}
|
||||
assert http_requests.count() == 2
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
{% if wscall.request.request_signature_key %}
|
||||
<li>{% trans "Request Signature Key:" %} {{ wscall.request.request_signature_key }}</li>
|
||||
{% endif %}
|
||||
{% if wscall.request.method == 'GET' and wscall.request.cache_duration %}
|
||||
<li>{% trans "Cache duration:" %} {{ wscall.request.cache_duration }}s</li>
|
||||
{% endif %}
|
||||
{% if wscall.request.timeout %}
|
||||
<li>{% trans "Timeout:" %} {{ wscall.request.timeout }}s</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import collections
|
||||
import hashlib
|
||||
import json
|
||||
import urllib.parse
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_str
|
||||
from django.core.cache import cache as django_cache
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from quixote import get_publisher, get_request
|
||||
|
||||
from wcs.api_utils import MissingSecret, get_secret_and_orig, sign_url
|
||||
|
@ -32,6 +34,7 @@ from .qommon.form import (
|
|||
CheckboxWidget,
|
||||
CompositeWidget,
|
||||
ComputedExpressionWidget,
|
||||
DurationWidget,
|
||||
RadiobuttonsWidget,
|
||||
StringWidget,
|
||||
WidgetDict,
|
||||
|
@ -64,6 +67,11 @@ def get_app_error_code(response, data, response_type):
|
|||
return app_error_code
|
||||
|
||||
|
||||
def get_cache_key(url):
|
||||
cache_key = url
|
||||
return force_str(hashlib.md5(force_bytes(cache_key)).hexdigest())
|
||||
|
||||
|
||||
def call_webservice(
|
||||
url,
|
||||
qs_data=None,
|
||||
|
@ -73,6 +81,7 @@ def call_webservice(
|
|||
post_formdata=None,
|
||||
formdata=None,
|
||||
cache=False,
|
||||
cache_duration=None,
|
||||
timeout=None,
|
||||
notify_on_errors=False,
|
||||
record_on_errors=False,
|
||||
|
@ -114,10 +123,16 @@ def call_webservice(
|
|||
|
||||
unsigned_url = url
|
||||
|
||||
if method == 'GET' and cache is True: # check cache
|
||||
request = get_request()
|
||||
if hasattr(request, 'wscalls_cache') and unsigned_url in request.wscalls_cache:
|
||||
return (None,) + request.wscalls_cache[unsigned_url]
|
||||
if method == 'GET':
|
||||
if cache is True: # check request cache
|
||||
request = get_request()
|
||||
if hasattr(request, 'wscalls_cache') and unsigned_url in request.wscalls_cache:
|
||||
return (None,) + request.wscalls_cache[unsigned_url]
|
||||
if cache_duration and int(cache_duration):
|
||||
cache_key = 'wscall-%s' % get_cache_key(unsigned_url)
|
||||
cached_result = django_cache.get(cache_key)
|
||||
if cached_result:
|
||||
return (None,) + cached_result
|
||||
|
||||
if request_signature_key:
|
||||
signature_key = str(WorkflowStatusItem.compute(request_signature_key))
|
||||
|
@ -165,6 +180,9 @@ def call_webservice(
|
|||
request = get_request()
|
||||
if cache is True and request and hasattr(request, 'wscalls_cache'):
|
||||
request.wscalls_cache[unsigned_url] = (status, data)
|
||||
if cache_duration:
|
||||
cache_key = 'wscall-%s' % get_cache_key(unsigned_url)
|
||||
django_cache.set(cache_key, (status, data), int(cache_duration))
|
||||
except ConnectionError as e:
|
||||
if not handle_connection_errors:
|
||||
raise e
|
||||
|
@ -246,7 +264,7 @@ class NamedWsCall(XmlStorableObject):
|
|||
|
||||
def export_request_to_xml(self, element, attribute_name, charset, **kwargs):
|
||||
request = getattr(self, attribute_name)
|
||||
for attr in ('url', 'request_signature_key', 'method', 'timeout'):
|
||||
for attr in ('url', 'request_signature_key', 'method', 'timeout', 'cache_duration'):
|
||||
ET.SubElement(element, attr).text = force_str(request.get(attr) or '', charset)
|
||||
for attr in ('qs_data', 'post_data'):
|
||||
data_element = ET.SubElement(element, attr)
|
||||
|
@ -259,7 +277,7 @@ class NamedWsCall(XmlStorableObject):
|
|||
|
||||
def import_request_from_xml(self, element, **kwargs):
|
||||
request = {}
|
||||
for attr in ('url', 'request_signature_key', 'method', 'timeout'):
|
||||
for attr in ('url', 'request_signature_key', 'method', 'timeout', 'cache_duration'):
|
||||
request[attr] = ''
|
||||
if element.find(attr) is not None and element.find(attr).text:
|
||||
request[attr] = force_str(element.find(attr).text)
|
||||
|
@ -386,6 +404,18 @@ class WsCallRequestWidget(CompositeWidget):
|
|||
if value and not value.isdecimal():
|
||||
raise ValueError(_('Timeout must be empty or a number.'))
|
||||
|
||||
self.add(
|
||||
DurationWidget,
|
||||
'cache_duration',
|
||||
value=value.get('cache_duration'),
|
||||
title=_('Cache Duration'),
|
||||
required=False,
|
||||
attrs={
|
||||
'data-dynamic-display-child-of': method_widget.get_name(),
|
||||
'data-dynamic-display-value': methods.get('GET'),
|
||||
},
|
||||
)
|
||||
|
||||
self.add(
|
||||
StringWidget,
|
||||
'timeout',
|
||||
|
@ -410,6 +440,7 @@ class WsCallRequestWidget(CompositeWidget):
|
|||
'post_formdata',
|
||||
'post_data',
|
||||
'timeout',
|
||||
'cache_duration',
|
||||
):
|
||||
if not self.include_post_formdata and name == 'post_formdata':
|
||||
continue
|
||||
|
|
Loading…
Reference in New Issue