mirror of
https://github.com/zotero/zotero.git
synced 2026-10-03 02:21:49 +00:00
Fix file downloads timing out after 30 seconds
We switched file downloads to use XHR to fix downloads failing via authenticated proxies in fx115+, but that made them subject to our default 30-second timeout in `Zotero.HTTP.request()`. That timeout uses the XHR `timeout` property, which applies to the whole request, even if data is actively being downloaded. As a result, file downloads would fail for people downloading large files or on very slow connections. This implements manual connect and inactivity timeouts when using `Zotero.HTTP.download()`. Currently, these both use `options.timeout`, or the default 30 seconds, but we could probably take separate options and have lower defaults now that they no longer apply to the whole request.
This commit is contained in:
parent
99b4171500
commit
0d934c846f
1 changed files with 75 additions and 9 deletions
|
|
@ -389,15 +389,54 @@ Zotero.HTTP = new function() {
|
|||
// Convert numbers to string to make Sinon happy
|
||||
xmlhttp.setRequestHeader(header, value);
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
|
||||
const defaultTimeout = 30000;
|
||||
let requestTimeout;
|
||||
let connectTimeout;
|
||||
let inactivityTimeout;
|
||||
if (options.timeout !== 0) {
|
||||
xmlhttp.timeout = options.timeout || 30000;
|
||||
// For downloads, manually implement connect and inactivity timeouts, since the XHR
|
||||
// `timeout` property applies to the whole request, even if data is being downloaded
|
||||
if (options.isDownload) {
|
||||
// TODO: Try a lower default connect timeout and take a separate option?
|
||||
connectTimeout = options.timeout || defaultTimeout;
|
||||
inactivityTimeout = options.timeout || defaultTimeout;
|
||||
}
|
||||
else {
|
||||
requestTimeout = options.timeout || defaultTimeout;
|
||||
}
|
||||
}
|
||||
let connectTimerID = null;
|
||||
let inactivityTimerID = null;
|
||||
let timedOutAfter;
|
||||
|
||||
function clearConnectTimer() {
|
||||
if (connectTimerID) {
|
||||
clearTimeout(connectTimerID);
|
||||
connectTimerID = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetInactivityTimer() {
|
||||
clearTimeout(inactivityTimerID);
|
||||
inactivityTimerID = setTimeout(() => {
|
||||
Zotero.warn(`Inactivity timeout for ${method} ${dispURL} -- aborting request`);
|
||||
timedOutAfter = inactivityTimeout;
|
||||
xmlhttp.abort();
|
||||
}, inactivityTimeout);
|
||||
}
|
||||
|
||||
if (requestTimeout) {
|
||||
xmlhttp.timeout = requestTimeout;
|
||||
xmlhttp.ontimeout = function() {
|
||||
deferred.reject(new Zotero.HTTP.TimeoutException(requestTimeout));
|
||||
};
|
||||
}
|
||||
else if (inactivityTimeout) {
|
||||
xmlhttp.onprogress = function () {
|
||||
resetInactivityTimer();
|
||||
};
|
||||
}
|
||||
|
||||
xmlhttp.ontimeout = function() {
|
||||
deferred.reject(new Zotero.HTTP.TimeoutException(options.timeout));
|
||||
};
|
||||
|
||||
// Provide caller with a callback to cancel a request in progress
|
||||
if (options.cancellerReceiver) {
|
||||
|
|
@ -411,7 +450,17 @@ Zotero.HTTP = new function() {
|
|||
});
|
||||
}
|
||||
|
||||
if (connectTimeout || inactivityTimeout) {
|
||||
xmlhttp.onloadstart = () => {
|
||||
clearConnectTimer();
|
||||
resetInactivityTimer();
|
||||
};
|
||||
}
|
||||
|
||||
xmlhttp.onloadend = async function() {
|
||||
clearConnectTimer();
|
||||
clearTimeout(inactivityTimerID);
|
||||
|
||||
var status = redirectStatus || xmlhttp.status;
|
||||
|
||||
try {
|
||||
|
|
@ -513,6 +562,11 @@ Zotero.HTTP = new function() {
|
|||
}
|
||||
Zotero.debug(msg, 1);
|
||||
|
||||
if (timedOutAfter) {
|
||||
deferred.reject(new Zotero.HTTP.TimeoutException(timedOutAfter));
|
||||
return;
|
||||
}
|
||||
|
||||
if (xmlhttp.status == 0) {
|
||||
try {
|
||||
this.checkSecurity(channel, { isProxyAuthRequest: options.isProxyAuthRequest });
|
||||
|
|
@ -538,19 +592,30 @@ Zotero.HTTP = new function() {
|
|||
}
|
||||
|
||||
// Send binary data
|
||||
let body;
|
||||
if (compressedBody) {
|
||||
let numBytes = compressedBody.length;
|
||||
let ui8Data = new Uint8Array(numBytes);
|
||||
for (let i = 0; i < numBytes; i++) {
|
||||
ui8Data[i] = compressedBody.charCodeAt(i) & 0xff;
|
||||
}
|
||||
xmlhttp.send(ui8Data);
|
||||
body = ui8Data;
|
||||
}
|
||||
// Send regular request
|
||||
else {
|
||||
xmlhttp.send(options.body || null);
|
||||
body = options.body || null;
|
||||
}
|
||||
|
||||
if (connectTimeout) {
|
||||
connectTimerID = setTimeout(() => {
|
||||
Zotero.warn(`Connect timeout for ${method} ${dispURL} -- aborting request`);
|
||||
timedOutAfter = connectTimeout;
|
||||
xmlhttp.abort();
|
||||
}, connectTimeout);
|
||||
}
|
||||
|
||||
xmlhttp.send(body);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
|
@ -598,6 +663,7 @@ Zotero.HTTP = new function() {
|
|||
uri,
|
||||
{
|
||||
...options,
|
||||
isDownload: true,
|
||||
responseType: 'blob',
|
||||
// Downloads can have channel notification callbacks, etc., so always do them for real
|
||||
noMock: true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue