From 5452ce6cffd9501531949e17988c202d8ce26a35 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 16 Oct 2020 12:18:42 +0200 Subject: [PATCH] MOBILE-3571 core: Split calls with too many parameters --- src/classes/site.ts | 13 ++- .../providers/module-prefetch-delegate.ts | 6 +- src/core/filter/providers/filter.ts | 6 +- src/providers/ws.ts | 86 ++++++++++++++++++- 4 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 10c9c3e27..b3285842f 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -20,7 +20,9 @@ import { CoreDbProvider } from '@providers/db'; import { CoreEventsProvider } from '@providers/events'; import { CoreFileProvider } from '@providers/file'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreWSProvider, CoreWSPreSets, CoreWSFileUploadOptions, CoreWSAjaxPreSets } from '@providers/ws'; +import { + CoreWSProvider, CoreWSPreSets, CoreWSFileUploadOptions, CoreWSAjaxPreSets, CoreWSPreSetsSplitRequest +} from '@providers/ws'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; @@ -138,6 +140,12 @@ export interface CoreSiteWSPreSets { * Component id. Optionally included when 'component' is set. */ componentId?: number; + + /** + * Whether to split a request if it has too many parameters. Sending too many parameters to the site + * can cause the request to fail (see PHP's max_input_vars). + */ + splitRequest?: CoreWSPreSetsSplitRequest; } /** @@ -650,7 +658,8 @@ export class CoreSite { siteUrl: this.siteUrl, cleanUnicode: this.cleanUnicode, typeExpected: preSets.typeExpected, - responseExpected: preSets.responseExpected + responseExpected: preSets.responseExpected, + splitRequest: preSets.splitRequest, }; if (wsPreSets.cleanUnicode && this.textUtils.hasUnicodeData(data)) { diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 53a236b09..888725bce 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -471,7 +471,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { preSets: CoreSiteWSPreSets = { cacheKey: this.getCourseUpdatesCacheKey(courseId), emergencyCache: false, // If downloaded data has changed and offline, just fail. See MOBILE-2085. - uniqueCacheKey: true + uniqueCacheKey: true, + splitRequest: { + param: 'tocheck', + maxLength: 10, + }, }; return site.read('core_course_check_updates', params, preSets).then((response) => { diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts index cfc5bc66d..c23f7a912 100644 --- a/src/core/filter/providers/filter.ts +++ b/src/core/filter/providers/filter.ts @@ -272,7 +272,11 @@ export class CoreFilterProvider { }, preSets = { cacheKey: this.getAvailableInContextsCacheKey(contextsToSend), - updateFrequency: CoreSite.FREQUENCY_RARELY + updateFrequency: CoreSite.FREQUENCY_RARELY, + splitRequest: { + param: 'contexts', + maxLength: 300, + }, }; return site.read('core_filters_get_available_in_context', data, preSets) diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 86fd9070a..9a11d4ee4 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -57,8 +57,34 @@ export interface CoreWSPreSets { * Defaults to false. Clean multibyte Unicode chars from data. */ cleanUnicode?: boolean; + + /** + * Whether to split a request if it has too many parameters. Sending too many parameters to the site + * can cause the request to fail (see PHP's max_input_vars). + */ + splitRequest?: CoreWSPreSetsSplitRequest; } +/** + * Options to split a request. + */ +export type CoreWSPreSetsSplitRequest = { + /** + * Name of the parameter used to split the request if too big. Must be an array parameter. + */ + param: string; + + /** + * Max number of entries sent per request. + */ + maxLength: number; + + /** + * Callback to combine the results. If not supplied, arrays in the result will be concatenated. + */ + combineCallback?: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any; +}; + /** * PreSets accepted by AJAX WS calls. */ @@ -622,7 +648,7 @@ export class CoreWSProvider { } /** - * Perform the post call and save the promise while waiting to be resolved. + * Perform the post call. It can be split into several requests. * * @param method The WebService method to be called. * @param siteUrl Complete site url to perform the call. @@ -639,6 +665,64 @@ export class CoreWSProvider { options['responseType'] = 'text'; } + if (!preSets.splitRequest || !ajaxData[preSets.splitRequest.param]) { + return this.performSinglePost(method, siteUrl, ajaxData, preSets, options); + } + + // Split the request into several requests if needed. + const promises: Promise[] = []; + + for (let i = 0; i < ajaxData[preSets.splitRequest.param].length; i += preSets.splitRequest.maxLength) { + // Limit the array sent. + const limitedData = Object.assign({}, ajaxData); + limitedData[preSets.splitRequest.param] = + ajaxData[preSets.splitRequest.param].slice(i, i + preSets.splitRequest.maxLength); + + promises.push(this.performSinglePost(method, siteUrl, limitedData, preSets, options)); + } + + return Promise.all(promises).then((results) => { + // Combine the results. + const firstResult = results.shift(); + + if (preSets.splitRequest.combineCallback) { + return results.reduce(preSets.splitRequest.combineCallback, firstResult); + } + + return results.reduce(this.combineObjectsArrays, firstResult); + }); + } + + /** + * Combine the arrays of two objects. + * + * @param object1 First object. + * @param object2 Second object. + * @return Combined object. + */ + protected combineObjectsArrays(object1: any, object2: any): any { + for (const name in object2) { + if (Array.isArray(object2[name])) { + object1[name] = object1[name].concat(object2[name]); + } + } + + return object1; + } + + /** + * Perform a single post request. + * + * @param method The WebService method to be called. + * @param siteUrl Complete site url to perform the call. + * @param ajaxData Arguments to pass to the method. + * @param preSets Extra settings and information. + * @param options Request options. + * @return Promise resolved with the response data in success and rejected with CoreWSError if it fails. + */ + protected performSinglePost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets, options: any) + : Promise { + // We add the method name to the URL purely to help with debugging. // This duplicates what is in the ajaxData, but that does no harm. // POST variables take precedence over GET.