diff --git a/guacamole-common-js/src/main/webapp/modules/Tunnel.js b/guacamole-common-js/src/main/webapp/modules/Tunnel.js index 54538eb5d..2565bd0b0 100644 --- a/guacamole-common-js/src/main/webapp/modules/Tunnel.js +++ b/guacamole-common-js/src/main/webapp/modules/Tunnel.js @@ -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