mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-896: Avoid XHR-related memory limitations by using Fetch API.
The Fetch API allows us to read HTTP responses as true streams, without building an in-memory string. We can the buffer things ourselves as we see fit, including as a Blob that can dynamically leverage disk storage for larger data.
This commit is contained in:
@@ -1346,13 +1346,14 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
var tunnel = this;
|
||||
|
||||
/**
|
||||
* The current, in-progress HTTP request. If no request is currently in
|
||||
* progress, this will be null.
|
||||
* AbortController instance which allows the current, in-progress HTTP
|
||||
* request to be aborted. If no request is currently in progress, this will
|
||||
* be null.
|
||||
*
|
||||
* @private
|
||||
* @type {XMLHttpRequest}
|
||||
* @type {AbortController}
|
||||
*/
|
||||
var xhr = null;
|
||||
var abortController = null;
|
||||
|
||||
/**
|
||||
* Additional headers to be sent in tunnel requests. This dictionary can be
|
||||
@@ -1364,23 +1365,6 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
*/
|
||||
var extraHeaders = extraTunnelHeaders || {};
|
||||
|
||||
/**
|
||||
* Adds the configured additional headers to the given request.
|
||||
*
|
||||
* @param {!XMLHttpRequest} request
|
||||
* The request where the configured extra headers will be added.
|
||||
*
|
||||
* @param {!object} headers
|
||||
* The headers to be added to the request.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function addExtraHeaders(request, headers) {
|
||||
for (var name in headers) {
|
||||
request.setRequestHeader(name, headers[name]);
|
||||
}
|
||||
}
|
||||
|
||||
this.sendMessage = function sendMessage(elements) {
|
||||
// Do nothing
|
||||
};
|
||||
@@ -1393,18 +1377,10 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
// Connection is now starting
|
||||
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
||||
|
||||
// Start a new connection
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.withCredentials = !!crossDomain;
|
||||
addExtraHeaders(xhr, extraHeaders);
|
||||
xhr.responseType = 'text';
|
||||
xhr.send(null);
|
||||
|
||||
var offset = 0;
|
||||
|
||||
// Create Guacamole protocol parser specifically for this connection
|
||||
// Create Guacamole protocol and UTF-8 parsers specifically for this
|
||||
// connection
|
||||
var parser = new Guacamole.Parser();
|
||||
var utf8Parser = new Guacamole.UTF8Parser();
|
||||
|
||||
// Invoke tunnel's oninstruction handler for each parsed instruction
|
||||
parser.oninstruction = function instructionReceived(opcode, args) {
|
||||
@@ -1412,51 +1388,62 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
||||
tunnel.oninstruction(opcode, args);
|
||||
};
|
||||
|
||||
// Continuously parse received data
|
||||
xhr.onreadystatechange = function readyStateChanged() {
|
||||
// Allow new request to be aborted
|
||||
abortController = new AbortController();
|
||||
|
||||
// Parse while data is being received
|
||||
if (xhr.readyState === 3 || xhr.readyState === 4) {
|
||||
// Stream using the Fetch API
|
||||
fetch(url, {
|
||||
headers : extraHeaders,
|
||||
credentials : crossDomain ? 'include' : 'same-origin',
|
||||
signal : abortController.signal
|
||||
})
|
||||
.then(function gotResponse(response) {
|
||||
|
||||
// Connection is open
|
||||
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
||||
// Reset state and close upon error
|
||||
if (!response.ok) {
|
||||
|
||||
var buffer = xhr.responseText;
|
||||
var length = buffer.length;
|
||||
if (tunnel.onerror)
|
||||
tunnel.onerror(new Guacamole.Status(
|
||||
Guacamole.Status.Code.fromHTTPCode(response.status), response.statusText));
|
||||
|
||||
// Parse only the portion of data which is newly received
|
||||
if (offset < length) {
|
||||
parser.receive(buffer.substring(offset));
|
||||
offset = length;
|
||||
}
|
||||
tunnel.disconnect();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Clean up and close when done
|
||||
if (xhr.readyState === 4)
|
||||
tunnel.disconnect();
|
||||
// Connection is open
|
||||
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
||||
|
||||
};
|
||||
var reader = response.body.getReader();
|
||||
var processReceivedText = function processReceivedText(result) {
|
||||
|
||||
// Reset state and close upon error
|
||||
xhr.onerror = function httpError() {
|
||||
// Clean up and close when done
|
||||
if (result.done) {
|
||||
tunnel.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail if file could not be downloaded via HTTP
|
||||
if (tunnel.onerror)
|
||||
tunnel.onerror(new Guacamole.Status(
|
||||
Guacamole.Status.Code.fromHTTPCode(xhr.status), xhr.statusText));
|
||||
// Parse only the portion of data which is newly received
|
||||
parser.receive(utf8Parser.decode(result.value));
|
||||
|
||||
tunnel.disconnect();
|
||||
};
|
||||
// Continue parsing when next chunk is received
|
||||
reader.read().then(processReceivedText);
|
||||
|
||||
};
|
||||
|
||||
// Schedule parse of first chunk
|
||||
reader.read().then(processReceivedText);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.disconnect = function disconnect() {
|
||||
|
||||
// Abort and dispose of XHR if a request is in progress
|
||||
if (xhr) {
|
||||
xhr.abort();
|
||||
xhr = null;
|
||||
// Abort any in-progress request
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortController = null;
|
||||
}
|
||||
|
||||
// Connection is now closed
|
||||
|
Reference in New Issue
Block a user