mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07: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;
|
var tunnel = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current, in-progress HTTP request. If no request is currently in
|
* AbortController instance which allows the current, in-progress HTTP
|
||||||
* progress, this will be null.
|
* request to be aborted. If no request is currently in progress, this will
|
||||||
|
* be null.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @type {XMLHttpRequest}
|
* @type {AbortController}
|
||||||
*/
|
*/
|
||||||
var xhr = null;
|
var abortController = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional headers to be sent in tunnel requests. This dictionary can be
|
* 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 || {};
|
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) {
|
this.sendMessage = function sendMessage(elements) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
};
|
};
|
||||||
@@ -1393,18 +1377,10 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
|||||||
// Connection is now starting
|
// Connection is now starting
|
||||||
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
||||||
|
|
||||||
// Start a new connection
|
// Create Guacamole protocol and UTF-8 parsers specifically for this
|
||||||
xhr = new XMLHttpRequest();
|
// connection
|
||||||
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
|
|
||||||
var parser = new Guacamole.Parser();
|
var parser = new Guacamole.Parser();
|
||||||
|
var utf8Parser = new Guacamole.UTF8Parser();
|
||||||
|
|
||||||
// Invoke tunnel's oninstruction handler for each parsed instruction
|
// Invoke tunnel's oninstruction handler for each parsed instruction
|
||||||
parser.oninstruction = function instructionReceived(opcode, args) {
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
||||||
@@ -1412,51 +1388,62 @@ Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTu
|
|||||||
tunnel.oninstruction(opcode, args);
|
tunnel.oninstruction(opcode, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Continuously parse received data
|
// Allow new request to be aborted
|
||||||
xhr.onreadystatechange = function readyStateChanged() {
|
abortController = new AbortController();
|
||||||
|
|
||||||
// Parse while data is being received
|
// Stream using the Fetch API
|
||||||
if (xhr.readyState === 3 || xhr.readyState === 4) {
|
fetch(url, {
|
||||||
|
headers : extraHeaders,
|
||||||
|
credentials : crossDomain ? 'include' : 'same-origin',
|
||||||
|
signal : abortController.signal
|
||||||
|
})
|
||||||
|
.then(function gotResponse(response) {
|
||||||
|
|
||||||
// Connection is open
|
// Reset state and close upon error
|
||||||
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
if (!response.ok) {
|
||||||
|
|
||||||
var buffer = xhr.responseText;
|
if (tunnel.onerror)
|
||||||
var length = buffer.length;
|
tunnel.onerror(new Guacamole.Status(
|
||||||
|
Guacamole.Status.Code.fromHTTPCode(response.status), response.statusText));
|
||||||
|
|
||||||
// Parse only the portion of data which is newly received
|
tunnel.disconnect();
|
||||||
if (offset < length) {
|
return;
|
||||||
parser.receive(buffer.substring(offset));
|
|
||||||
offset = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up and close when done
|
// Connection is open
|
||||||
if (xhr.readyState === 4)
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
||||||
tunnel.disconnect();
|
|
||||||
|
|
||||||
};
|
var reader = response.body.getReader();
|
||||||
|
var processReceivedText = function processReceivedText(result) {
|
||||||
|
|
||||||
// Reset state and close upon error
|
// Clean up and close when done
|
||||||
xhr.onerror = function httpError() {
|
if (result.done) {
|
||||||
|
tunnel.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Fail if file could not be downloaded via HTTP
|
// Parse only the portion of data which is newly received
|
||||||
if (tunnel.onerror)
|
parser.receive(utf8Parser.decode(result.value));
|
||||||
tunnel.onerror(new Guacamole.Status(
|
|
||||||
Guacamole.Status.Code.fromHTTPCode(xhr.status), xhr.statusText));
|
|
||||||
|
|
||||||
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() {
|
this.disconnect = function disconnect() {
|
||||||
|
|
||||||
// Abort and dispose of XHR if a request is in progress
|
// Abort any in-progress request
|
||||||
if (xhr) {
|
if (abortController) {
|
||||||
xhr.abort();
|
abortController.abort();
|
||||||
xhr = null;
|
abortController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection is now closed
|
// Connection is now closed
|
||||||
|
Reference in New Issue
Block a user