@@ -365,6 +365,10 @@ settings = {
365365 * @desc Allows to retry files reading if original URL fails
366366 * @private */
367367 FilesRemap: { 'https://root.cern/': 'https://root-eos.web.cern.ch/' },
368+ /** @summary THttpServer read timeout in ms
369+ * @desc Configures timeout for requests to THttpServer
370+ * @default 0 */
371+ ServerTimeout: 0,
368372 /** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */
369373 WithCredentials: false,
370374 /** @summary Skip streamer infos from the GUI */
@@ -968,13 +972,21 @@ function findFunction(name) {
968972/** @summary Method to create http request, without promise can be used only in browser environment
969973 * @private */
970974function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise, tmout) {
975+ function handle_error(xhr, message, code, abort_reason) {
976+ if (!xhr.did_abort) {
977+ xhr.did_abort = abort_reason || true;
978+ xhr.abort();
979+ }
980+ if (!xhr.did_error || abort_reason)
981+ console.warn(message);
982+ if (!xhr.did_error) {
983+ xhr.did_error = true;
984+ xhr.error_callback(Error(message), code);
985+ }
986+ }
971987 function configureXhr(xhr) {
972988 xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {};
973- xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function(err) {
974- if (err?.message)
975- console.warn(err.message);
976- this.http_callback(null);
977- }.bind(xhr);
989+ xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function() { this.http_callback(null); };
978990
979991 if (!kind)
980992 kind = 'buf';
@@ -1010,11 +1022,8 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10101022
10111023 if (settings.HandleWrongHttpResponse && (method === 'GET') && isFunc(xhr.addEventListener)) {
10121024 xhr.addEventListener('progress', function(oEvent) {
1013- if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size)) {
1014- this.did_abort = true;
1015- this.abort();
1016- this.error_callback(Error(`Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`), 598);
1017- }
1025+ if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size))
1026+ handle_error(this, `Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`, 598);
10181027 }.bind(xhr));
10191028 }
10201029
@@ -1024,11 +1033,8 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10241033
10251034 if ((this.readyState === 2) && this.expected_size) {
10261035 const len = parseInt(this.getResponseHeader('Content-Length'));
1027- if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) {
1028- this.did_abort = 'large';
1029- this.abort();
1030- return this.error_callback(Error(`Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`), 599);
1031- }
1036+ if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse)
1037+ return handle_error(this, `Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`, 599, 'large');
10321038 }
10331039
10341040 if (this.readyState !== 4)
@@ -1037,7 +1043,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10371043 if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 &&
10381044 // in these special cases browsers not always set status
10391045 !((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0))))
1040- return this.error_callback(Error( `Fail to load url ${url}`) , this.status);
1046+ return handle_error(this, `Fail to load url ${url}`, this.status);
10411047
10421048 if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) {
10431049 // special handling of gzip JSON objects in Node.js
@@ -1084,11 +1090,7 @@ function createHttpRequest(url, kind, user_accept_callback, user_reject_callback
10841090
10851091 if (tmout && Number.isFinite(tmout)) {
10861092 xhr.timeout = tmout;
1087- xhr.ontimeout = function() {
1088- this.did_abort = true;
1089- this.abort();
1090- this.error_callback(Error(`Request ${url} timeout`));
1091- };
1093+ xhr.ontimeout = function() { handle_error(this, `Request ${url} timeout set ${tmout} ms`, 600, 'timeout'); };
10921094 }
10931095
10941096 return xhr;
@@ -124396,15 +124398,12 @@ class TFile {
124396124398 if (file.fAcceptRanges && first_block)
124397124399 totalsz = Math.max(totalsz, 1e5);
124398124400
124399- return createHttpRequest(fullurl, 'buf', read_callback, undefined, true).then(xhr => {
124401+ return createHttpRequest(fullurl, 'buf', read_callback, undefined, true, file.fTimeout ).then(xhr => {
124400124402 if (file.fAcceptRanges) {
124401124403 xhr.setRequestHeader('Range', ranges);
124402124404 xhr.expected_size = Math.max(Math.round(1.1 * totalsz), totalsz + 200); // 200 if offset for the potential gzip
124403124405 }
124404124406
124405- if (file.fTimeout)
124406- xhr.timeout = file.fTimeout;
124407-
124408124407 if (isFunc(progress_callback) && isFunc(xhr.addEventListener)) {
124409124408 let sum1 = 0, sum2 = 0, sum_total = 0;
124410124409 for (let n = 1; n < place.length; n += 2) {
@@ -167241,7 +167240,7 @@ class HierarchyPainter extends BasePainter {
167241167240 handleAfterRequest(findFunction(item._after_request)); // v6 support
167242167241 } else
167243167242 handleAfterRequest(draw_handle?.after_request);
167244- }, undefined, true).then(xhr => {
167243+ }, undefined, true, settings.ServerTimeout ).then(xhr => {
167245167244 itemreq = xhr;
167246167245 xhr.send(null);
167247167246 });
@@ -168379,6 +168378,12 @@ function readStyleFromURL(url) {
168379168378 if (d.has('prefer_saved_points'))
168380168379 settings.PreferSavedPoints = true;
168381168380
168381+ if (d.has('tmout'))
168382+ settings.ServerTimeout = parseFloat(d.get('tmout'));
168383+
168384+ if (d.has('ftmout'))
168385+ settings.FilesTimeout = parseFloat(d.get('ftmout'));
168386+
168382168387 const tf1_style = d.get('tf1');
168383168388 if (tf1_style === 'curve')
168384168389 settings.FuncAsCurve = true;
0 commit comments