MDL-65794 core: allow ajax calls to specify a cache key

This allows for better caching capabilities on servers. If a
cache key is passed and the web service call does not require
the user to be logged in we will attempt to use GET for the
request. This allows for things like proxy caching on URLs.
The cache key must be changed if we do not want to retrieve
what has been cached and want to perform the request again.
This commit is contained in:
Mark Nelson 2019-06-24 23:38:36 +08:00
parent 7fa4e41052
commit 6a1ad7c677
5 changed files with 74 additions and 5 deletions

View File

@ -28,4 +28,6 @@
*/
define('NO_MOODLE_COOKIES', true);
define('ALLOW_GET_PARAMETERS', true);
require_once('service.php');

View File

@ -38,9 +38,24 @@ require_once($CFG->libdir . '/externallib.php');
define('PREFERRED_RENDERER_TARGET', RENDERER_TARGET_GENERAL);
$rawjson = file_get_contents('php://input');
$arguments = '';
$cacherequest = false;
if (defined('ALLOW_GET_PARAMETERS')) {
$arguments = optional_param('args', '', PARAM_RAW);
$cachekey = optional_param('cachekey', '', PARAM_INT);
if ($cachekey && $cachekey > 0 && $cachekey <= time()) {
$cacherequest = true;
}
}
// Either we are not allowing GET parameters or we didn't use GET because
// we did not pass a cache key or the URL was too long.
if (empty($arguments)) {
$arguments = file_get_contents('php://input');
}
$requests = json_decode($arguments, true);
$requests = json_decode($rawjson, true);
if ($requests === null) {
$lasterror = json_last_error_msg();
throw new coding_exception('Invalid json in request: ' . $lasterror);
@ -54,6 +69,7 @@ $settings->set_fileurl(true);
$settings->set_filter(true);
$settings->set_raw(false);
$haserror = false;
foreach ($requests as $request) {
$response = array();
$methodname = clean_param($request['methodname'], PARAM_ALPHANUMEXT);
@ -64,7 +80,19 @@ foreach ($requests as $request) {
$responses[$index] = $response;
if ($response['error']) {
// Do not process the remaining requests.
$haserror = true;
break;
}
}
if ($cacherequest && !$haserror) {
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age=' . $lifetime . ', immutable');
header('Accept-Ranges: none');
}
echo json_encode($responses);

View File

@ -1 +1 @@
define(["jquery","core/config","core/log","core/url"],function(a,b,c,d){var e=!1,f=function(a){var b,c,e,f=this,g=null,h=0;if(a.error)for(;h<f.length;h++)b=f[h],b.deferred.reject(a);else{for(h=0;h<f.length;h++){if(b=f[h],c=a[h],"undefined"==typeof c){g=new Error("missing response");break}if(c.error!==!1){g=c.exception,e=f[h].nosessionupdate;break}b.deferred.resolve(c.data)}null!==g&&("servicerequireslogin"!==g.errorcode||e?f.forEach(function(a){a.deferred.reject(g)}):window.location=d.relativeUrl("/login/index.php"))}},g=function(a,b,d){var f=this,g=0;for(g=0;g<f.length;g++){var h=f[g];e?(c.error("Page unloaded."),c.error(d)):h.deferred.reject(d)}};return{call:function(c,d,h,i,j){a(window).bind("beforeunload",function(){e=!0});var k,l=[],m=[],n=[],o="";for("undefined"==typeof h&&(h=!0),"undefined"==typeof d&&(d=!0),"undefined"==typeof j&&(j=0),"undefined"==typeof i&&(i=!1),k=0;k<c.length;k++){var p=c[k];l.push({index:k,methodname:p.methodname,args:p.args}),p.nosessionupdate=i,p.deferred=a.Deferred(),m.push(p.deferred.promise()),"undefined"!=typeof p.done&&p.deferred.done(p.done),"undefined"!=typeof p.fail&&p.deferred.fail(p.fail),p.index=k,n.push(p.methodname)}o=n.length<=5?n.sort().join():n.length+"-method-calls",l=JSON.stringify(l);var q={type:"POST",data:l,context:c,dataType:"json",processData:!1,async:d,contentType:"application/json",timeout:j},r="service.php",s=b.wwwroot+"/lib/ajax/";return h?s+=r+"?sesskey="+b.sesskey+"&info="+o:(r="service-nologin.php",s+=r+"?info="+o),i&&(s+="&nosessionupdate=true"),d?a.ajax(s,q).done(f).fail(g):(q.success=f,q.error=g,a.ajax(s,q)),m}}});
define(["jquery","core/config","core/log","core/url"],function(a,b,c,d){var e=!1,f=function(a){var b,c,e,f=this,g=null,h=0;if(a.error)for(;h<f.length;h++)b=f[h],b.deferred.reject(a);else{for(h=0;h<f.length;h++){if(b=f[h],c=a[h],"undefined"==typeof c){g=new Error("missing response");break}if(c.error!==!1){g=c.exception,e=f[h].nosessionupdate;break}b.deferred.resolve(c.data)}null!==g&&("servicerequireslogin"!==g.errorcode||e?f.forEach(function(a){a.deferred.reject(g)}):window.location=d.relativeUrl("/login/index.php"))}},g=function(a,b,d){var f=this,g=0;for(g=0;g<f.length;g++){var h=f[g];e?(c.error("Page unloaded."),c.error(d)):h.deferred.reject(d)}};return{call:function(c,d,h,i,j,k){a(window).bind("beforeunload",function(){e=!0});var l,m=[],n=[],o=[],p="",q=2e3;for("undefined"==typeof h&&(h=!0),"undefined"==typeof d&&(d=!0),"undefined"==typeof j&&(j=0),"undefined"==typeof k?k=null:(k=parseInt(k),k<=0?k=null:k||(k=null)),"undefined"==typeof i&&(i=!1),l=0;l<c.length;l++){var r=c[l];m.push({index:l,methodname:r.methodname,args:r.args}),r.nosessionupdate=i,r.deferred=a.Deferred(),n.push(r.deferred.promise()),"undefined"!=typeof r.done&&r.deferred.done(r.done),"undefined"!=typeof r.fail&&r.deferred.fail(r.fail),r.index=l,o.push(r.methodname)}p=o.length<=5?o.sort().join():o.length+"-method-calls",m=JSON.stringify(m);var s={type:"POST",context:c,dataType:"json",processData:!1,async:d,contentType:"application/json",timeout:j},t="service.php",u=b.wwwroot+"/lib/ajax/";if(h?u+=t+"?sesskey="+b.sesskey+"&info="+p:(t="service-nologin.php",u+=t+"?info="+p,k&&(u+="&cachekey="+k,s.type="GET")),i&&(u+="&nosessionupdate=true"),"POST"===s.type)s.data=m;else{var v=u+"&args="+encodeURIComponent(m);v.length>q?(s.type="POST",s.data=m):u=v}return d?a.ajax(u,s).done(f).fail(g):(s.success=f,s.error=g,a.ajax(u,s)),n}}});

View File

@ -137,9 +137,13 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
* @param {Boolean} nosessionupdate Optional, defaults to false.
* If true, the timemodified for the session will not be updated.
* @param {Integer} timeout number of milliseconds to wait for a response. Defaults to no limit.
* @param {Integer} cachekey This is used in order to identify the request. If this id changes then we
* will be sending a different URL and any caching (eg. browser, proxy) knows that it
* should perform another request and not use the cache. Note - this variable is only
* used when we are calling 'service-nologin.php'. See MDL-65794.
* @return {Promise[]} Array of promises that will be resolved when the ajax call returns.
*/
call: function(requests, async, loginrequired, nosessionupdate, timeout) {
call: function(requests, async, loginrequired, nosessionupdate, timeout, cachekey) {
$(window).bind('beforeunload', function() {
unloading = true;
});
@ -149,6 +153,8 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
methodInfo = [],
requestInfo = '';
var maxUrlLength = 2000;
if (typeof loginrequired === "undefined") {
loginrequired = true;
}
@ -158,6 +164,16 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
if (typeof timeout === 'undefined') {
timeout = 0;
}
if (typeof cachekey === 'undefined') {
cachekey = null;
} else {
cachekey = parseInt(cachekey);
if (cachekey <= 0) {
cachekey = null;
} else if (!cachekey) {
cachekey = null;
}
}
if (typeof nosessionupdate === "undefined") {
nosessionupdate = false;
@ -193,7 +209,6 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
ajaxRequestData = JSON.stringify(ajaxRequestData);
var settings = {
type: 'POST',
data: ajaxRequestData,
context: requests,
dataType: 'json',
processData: false,
@ -207,6 +222,10 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
if (!loginrequired) {
script = 'service-nologin.php';
url += script + '?info=' + requestInfo;
if (cachekey) {
url += '&cachekey=' + cachekey;
settings.type = 'GET';
}
} else {
url += script + '?sesskey=' + config.sesskey + '&info=' + requestInfo;
}
@ -215,6 +234,19 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
url += '&nosessionupdate=true';
}
if (settings.type === 'POST') {
settings.data = ajaxRequestData;
} else {
var urlUseGet = url + '&args=' + encodeURIComponent(ajaxRequestData);
if (urlUseGet.length > maxUrlLength) {
settings.type = 'POST';
settings.data = ajaxRequestData;
} else {
url = urlUseGet;
}
}
// Jquery deprecated done and fail with async=false so we need to do this 2 ways.
if (async) {
$.ajax(url, settings)

View File

@ -3,6 +3,13 @@ information provided here is intended especially for developers.
This information is intended for authors of webservices, not people writing webservice clients.
=== 3.8 ===
* Ajax calls can now specify a cache key. This allows for better caching capabilities on servers. If a cache key
is passed and the web service call does not require the user to be logged in we will attempt to use GET for the
request. This allows for things like proxy caching on URLs. The cache key must be changed if we do not want to
retrieve what has been cached and want to perform the request again.
=== 3.7 ===
* External function core_webservice_external::get_site_info() now returns the current site theme (for the user).