From aec7c593d84e3b4631ce1280f2b3d787030afa88 Mon Sep 17 00:00:00 2001 From: Alexander Kastius Date: Wed, 17 Aug 2016 17:25:27 +0200 Subject: [PATCH] Moved sockets. Fixed sockets. --- app/assets/javascripts/editor/editor.js.erb | 10 +- .../javascripts/editor/evaluation.js.erb | 12 +- .../javascripts/editor/execution.js.erb | 46 +++++ .../javascripts/editor/submissions.js.erb | 4 +- app/assets/javascripts/editor/turtle.js.erb | 4 + .../javascripts/editor/websocket.js.erb | 177 +++++++++--------- 6 files changed, 147 insertions(+), 106 deletions(-) create mode 100644 app/assets/javascripts/editor/execution.js.erb diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index e0c64faa..b16892ff 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -30,11 +30,8 @@ var CodeOceanEditor = { output_mode_is_streaming: true, runmode: this.NONE, - websocket: null, numMessages: 0, prompt: $('#prompt'), - commands: ['input', 'write', 'turtle', 'turtlebatch', 'render', 'exit', 'timeout', 'status'], - streams: ['stdin', 'stdout', 'stderr'], lastCopyText: null, autosaveTimer: null, @@ -42,7 +39,6 @@ var CodeOceanEditor = { ENTER_KEY_CODE: 13, - flowrOutputBuffer: "", QaApiOutputBuffer: {'stdout': '', 'stderr': ''}, flowrResultHtml: '
', @@ -255,9 +251,9 @@ var CodeOceanEditor = { }, initializeEventHandlers: function () { - $(document).on('click', '#results a', this.showOutput); - $(document).on('keypress', this.handleKeyPress); - $('a[data-toggle="tab"]').on('show.bs.tab', this.storeTab); + $(document).on('click', '#results a', this.showOutput.bind(this)); + $(document).on('keypress', this.handleKeyPress.bind(this)); + $('a[data-toggle="tab"]').on('show.bs.tab', this.storeTab.bind(this)); this.initializeFileTreeButtons(); this.initializeWorkflowButtons(); this.initializeWorkspaceButtons(); diff --git a/app/assets/javascripts/editor/evaluation.js.erb b/app/assets/javascripts/editor/evaluation.js.erb index a062e4e5..0f944239 100644 --- a/app/assets/javascripts/editor/evaluation.js.erb +++ b/app/assets/javascripts/editor/evaluation.js.erb @@ -1,12 +1,7 @@ CodeOceanEditorEvaluation = { chunkBuffer: [{streamedResponse: true}], - evaluateCode: function (url, onmessageFunction) { - this.initWebsocketConnection(url, onmessageFunction);; - }, - - handleScoringResponse: function (websocket_event) { - var results = JSON.parse(websocket_event.data); + handleScoringResponse: function (results) { this.printScoringResults(results); var score = _.reduce(results, function (sum, result) { return sum + result.score * result.weight; @@ -16,8 +11,7 @@ CodeOceanEditorEvaluation = { this.showTab(2); }, - handleTestResponse: function (websocket_event) { - var result = JSON.parse(websocket_event.data); + handleTestResponse: function (result) { this.clearOutput(); this.printOutput(result, false, 0); if (this.qa_api) { @@ -111,7 +105,7 @@ CodeOceanEditorEvaluation = { this.createSubmission('#assess', null, function (response) { this.showSpinner($('#assess')); var url = response.score_url; - this.evaluateCode(url, this.handleScoringResponse.bind(this)); + this.initializeSocketForScoring(url); }.bind(this)); }, diff --git a/app/assets/javascripts/editor/execution.js.erb b/app/assets/javascripts/editor/execution.js.erb new file mode 100644 index 00000000..53b53ff7 --- /dev/null +++ b/app/assets/javascripts/editor/execution.js.erb @@ -0,0 +1,46 @@ +CodeOceanEditorWebsocket = { + websocket: null, + + createSocketUrl: function(url) { + return '<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url; + }, + + initializeSocket: function(url) { + this.websocket = new CommandSocket(this.createSocketUrl(url), + function (evt) { + this.resetOutputTab(); + }.bind(this) + ); + this.websocket.onError(this.showWebsocketError.bind(this)); + }, + + initializeSocketForTesting: function(url) { + this.initializeSocket(url); + this.websocket.on('default',this.handleTestResponse.bind(this)); + }, + + initializeSocketForScoring: function(url) { + this.initializeSocket(url); + this.websocket.on('default',this.handleScoringResponse.bind(this)) + }, + + initializeSocketForRunning: function(url) { + this.initializeSocket(url); + this.websocket.on('input',this.showPrompt.bind(this)); + this.websocket.on('write', this.printWebsocketOutput.bind(this)); + this.websocket.on('turtle', this.handleTurtleCommand.bind(this)); + this.websocket.on('turtlebatch', this.handleTurtlebatchCommand.bind(this)); + this.websocket.on('render', this.renderWebsocketOutput.bind(this)); + this.websocket.on('exit', this.handleExitCommand.bind(this)); + this.websocket.on('timeout', this.showTimeoutMessage.bind(this)); + this.websocket.on('status', this.showStatus.bind(this)); + }, + + handleExitCommand: function() { + this.killWebsocketAndContainer(); + this.handleQaApiOutput(); + this.handleStderrOutputForFlowr(); + this.augmentStacktraceInOutput(); + this.cleanUpTurtle(); + } +}; \ No newline at end of file diff --git a/app/assets/javascripts/editor/submissions.js.erb b/app/assets/javascripts/editor/submissions.js.erb index f2712f02..f752dde2 100644 --- a/app/assets/javascripts/editor/submissions.js.erb +++ b/app/assets/javascripts/editor/submissions.js.erb @@ -134,7 +134,7 @@ CodeOceanEditorSubmissions = { this.showSpinner($('#run')); this.toggleButtonStates(); var url = response.run_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename); - this.evaluateCode(url, function(evt) { this.parseCanvasMessage(evt.data, true); }.bind(this)); + this.initializeSocketForRunning(url); }.bind(this)); } }, @@ -154,7 +154,7 @@ CodeOceanEditorSubmissions = { this.createSubmission('#test', null, function(response) { this.showSpinner($('#test')); var url = response.test_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename); - this.evaluateCode(url, this.handleTestResponse.bind(this)); + this.initializeSocketForTesting(); }.bind(this)); } }, diff --git a/app/assets/javascripts/editor/turtle.js.erb b/app/assets/javascripts/editor/turtle.js.erb index 87e5bdc8..daebd59c 100644 --- a/app/assets/javascripts/editor/turtle.js.erb +++ b/app/assets/javascripts/editor/turtle.js.erb @@ -16,6 +16,8 @@ CodeOceanEditorTurtle = { }, handleTurtleCommand: function (msg) { + this.initTurtle(); + this.showCanvas(); if (msg.action in this.turtlescreen) { var result = this.turtlescreen[msg.action].apply(this.turtlescreen, msg.args); this.websocket.send(JSON.stringify({cmd: 'result', 'result': result})); @@ -26,6 +28,8 @@ CodeOceanEditorTurtle = { }, handleTurtlebatchCommand: function (msg) { + this.initTurtle(); + this.showCanvas(); for (var i = 0; i < msg.batch.length; i++) { var cmd = msg.batch[i]; this.turtlescreen[cmd[0]].apply(this.turtlescreen, cmd[1]); diff --git a/app/assets/javascripts/editor/websocket.js.erb b/app/assets/javascripts/editor/websocket.js.erb index f9d5466a..4c3f9787 100644 --- a/app/assets/javascripts/editor/websocket.js.erb +++ b/app/assets/javascripts/editor/websocket.js.erb @@ -1,89 +1,90 @@ -CodeOceanEditorWebsocket = { - initWebsocketConnection: function (url, onmessageFunction) { - //TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss) - //causes: Puma::HttpParserError: Invalid HTTP format, parsing fails. - //TODO: make sure that this gets cached. - this.websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url); - this.websocket.onopen = function (evt) { - this.resetOutputTab(); - }.bind(this); // todo show some kind of indicator for established connection - this.websocket.onclose = function (evt) { /* expected at some point */ - }.bind(this); - this.websocket.onmessage = onmessageFunction; - this.websocket.onerror = function (evt) { - this.showWebsocketError(); - }.bind(this); - this.websocket.flush = function () { - this.send('\n'); - } - }, - - //ToDo: Move websocket and commands variable in here - executeWebsocketCommand: function (msg) { - if ($.inArray(msg.cmd, this.commands) == -1) { - console.log("Unknown command: " + msg.cmd); - // skipping unregistered commands is required - // as we may receive mirrored response due to internal behaviour - return; - } - switch (msg.cmd) { - case 'input': - this.showPrompt(msg); - break; - case 'write': - this.printWebsocketOutput(msg); - break; - case 'turtle': - this.initTurtle(); - this.showCanvas(); - this.handleTurtleCommand(msg); - break; - case 'turtlebatch': - this.initTurtle(); - this.showCanvas(); - this.handleTurtlebatchCommand(msg); - break; - case 'render': - this.renderWebsocketOutput(msg); - break; - case 'exit': - this.killWebsocketAndContainer(); - this.handleQaApiOutput(); - this.handleStderrOutputForFlowr(); - this.augmentStacktraceInOutput(); - this.cleanUpTurtle(); - break; - case 'timeout': - // just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes. - this.showTimeoutMessage(); - break; - case 'status': - this.showStatus(msg); - break; - } - }, - - parseCanvasMessage: function (message, recursive) { - var msg; - message = message.replace(/^\s+|\s+$/g, ""); - try { - // todo validate json instead of catching - msg = JSON.parse(message); - } catch (e) { - if (!recursive) { - return false; - } - // why does docker sometimes send multiple commands at once? - message = message.replace(/^\s+|\s+$/g, ""); - var messages = message.split("\n"); - for (var i = 0; i < messages.length; i++) { - if (!messages[i]) { - continue; - } - this.parseCanvasMessage(messages[i], false); - } - return; - } - this.executeWebsocketCommand(msg); +CommandSocket = function(url, onOpen) { + this.handlers = {}; + this.websocket = new WebSocket(url); + this.websocket.onopen = onOpen; + this.websocket.onmessage = this.onMessage.bind(this); + this.websocket.flush = function () { + this.send('\n'); } -}; \ No newline at end of file +}; + +CommandSocket.prototype.onError = function(callback){ + this.websocket.onerror = callback +}; + +/** + * Allows it to register an event-handler on the given cmd. + * The handler needs to accept one argument, the message. + * There is only handler per command at the moment. + * @param command + * @param handler + */ +CommandSocket.prototype.on = function(command, handler) { + this.handlers[command] = handler; +}; + + +/** + * Used to initialize the recursive message parser. + * @param event + */ +CommandSocket.prototype.onMessage = function(event) { + //Parses the message (serches for linebreaks) and executes every contained cmd. + this.parseMessage(event.data, true) +}; + +/** + * Parses a message, checks wether it contains multiple commands (seperated by linebreaks) + * This needs to be done because of the behavior of the docker-socket connection. + * Because of this, sometimes multiple commands might be executed in one message. + * @param message + * @param recursive + * @returns {boolean} + */ +CommandSocket.prototype.parseMessage = function(message, recursive) { + var msg; + var message_string = message.replace(/^\s+|\s+$/g, ""); + try { + // todo validate json instead of catching + msg = JSON.parse(message_string); + } catch (e) { + if (!recursive) { + return false; + } + // why does docker sometimes send multiple commands at once? + message_string = message_string.replace(/^\s+|\s+$/g, ""); + var messages = message_string.split("\n"); + for (var i = 0; i < messages.length; i++) { + if (!messages[i]) { + continue; + } + this.parseMessage(messages[i], false); + } + return; + } + this.executeCommand(msg); +}; + +/** + * Executes the handler that is registered for a certain command. + * Does nothing if the command was not specified yet. + * If there is a null-handler (defined with on('default',func)) this gets + * executed if the command was not registered or the message has no cmd prop. + * @param cmd + */ +CommandSocket.prototype.executeCommand = function(cmd) { + if ('cmd' in cmd && cmd.cmd in this.handlers) { + this.handlers[cmd.cmd](cmd); + } else if ('default' in this.handlers) { + this.handlers['default'](cmd); + } +}; + +/** + * Used to send a message through the socket. + * If data is not a string we'll try use jsonify to make it a string. + * @param data + */ +CommandSocket.prototype.send = function(data) { + this.websocket.send(data); +};