Dancer2 application: streaming content blocks server and client. How to avoid blocking? - perl

tl;dr
I'm streaming content with Dancer2's keywords delayed, flush, content, done and it blocks the server while streaming until I call done. How can I avoid that?
More detailed
I wrote a Dancer2 application that calls an external command (a shell/Perl script) when the user hits a specific button. I want the output of that command to appear in the browser in a <pre> section and I want to refresh that section as new lines arrive. Similar to a tail -f but in the browser. My external command runs several seconds, possibly up to 10 minutes, so I run it asynchronously.
My first approach was to completely detach the program from the Webserver using double-fork, exec, ssid, and closing/reopening the program's STDIN/OUT/ERR so that the command's output goes to a temporary logfile. Then I set up AJAX calls (one per second) so that my Dancer2 application reads the new lines from the logfile and returns them to the client until the PID of the external command would disappear. This worked like a charm until the moment when my "external command" issued ssh commands to contact some other server and return that output as well. ssh doesn't work properly when run without a terminal and the ssh did not produce any output. Think of ssh $host "ls -l", which gave no output.
So I switched to Dancer2's delayed mechanism like shown in the code below. I took the CSV example from the Dancer2 docs as a template. Again, it works like a charm, but while the command is running and new lines appear in the browser, the server is blocked. When I click some other link on the Webpage, I only see an hour glass until the command is over. It looks like the server is single-process and single threaded.
index.tt
<script>
function start_command( event ) {
$('#out_win').empty();
var last_response_len = false;
$.ajax({
url: '/ajax/start',
xhrFields: {
onprogress: function(evt){
/* make "this_response" only contain the new lines: */
var this_response;
var response = evt.currentTarget.response;
if ( last_response_len === false ) {
this_response = response;
last_response_len = response.length;
} else {
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
/* add those new lines to <pre> and scroll down */
var pre = $('#out_win');
pre.append(this_response);
pre.scrollTop(pre.prop('scrollHeight'));
}
},
success: function(result, textStatus, jqXHR) {
alert("Done streaming, result="+result);
},
error: function( jqXHR, textStatus, errorThrown ) {
alert("error; status=" + textStatus);
},
});
event.preventDefault();
}
</script>
<div class="container">
<div> <%# Links %>
Start external command</br>
Show some other page
<div>
<div> <%# output window %>
<pre class="pre-scrollable" id="out_win"></pre>
</div>
</div>
Streaming.pm
package Streaming;
use Dancer2;
################################################################
#
################################################################
get '/' => sub {
template 'index';
};
################################################################
#
################################################################
get '/ajax/start' => sub {
delayed {
flush; # streaming content
# "stream" something. Actually I start an external program here
# with open(..., "$pgm |") and stream its output line my line.
foreach my $line ( 1 .. 10 ) {
content "This is line $line\n";
sleep(1);
}
done; # close user connection
}
on_error => sub {
my ($error) = #_;
warning 'Failed to stream to user: ' . request->remote_address;
};
};
true;
I'm using
Dancer2 0.204002 as installed via apt install libdancer2-perl on
Ubuntu 17.04
no further Webserver, i.e. I'm using the server that ships with Dancer2, started with plackup -a bin/app.psgi
jQuery 2.2.4
Bootstrap 3.3.7
Perl 5.18+
My questions are:
Am I doing something completely wrong wrt. the delayed keyword?
Or am I using the wrong Webserver for this task? I'd love to stick with
the Webserver that comes with Dancer2 because it's so simple to use and
I don't need a throughput like e.g. Google does. We will have 1-3 users
a day and mostly not at the same time, but a blocking Webapplication
would be a no-go.

Related

Vscode cant find function defined in contextBridge.exposeInMainWorld

I just started modifying the electron-react-boilerplate project and tried doing the following:
In the App.tsx file I added a button:
const ping = () => {
electron.ipcRenderer.myAwesomePing('Hello world!');
};
const Hello = () => {
return (
<div>
...
<button type="button" onClick={() => ping()}>
Ping!
</button>
...
</div>
);
};
And in the preload.js file, I added the corresponding call for myAwesomePing:
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
myAwesomePing(text) {
ipcRenderer.send('ipc-example', text);
},
When I run the code, everything seems to work fine and I receive the ping through the context-bridge on the main process.
But, visual studio code keeps complaining, that it
Cannot find name 'electron'. Did you mean 'Electron'?
inside App.tsx.
Is this because I am missing something or just a bug in vscode? Is there maybe a build step necessary to create the connection?
ERB recently added support for globally exposed variables via preload:
https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/src/renderer/preload.d.ts
Copy and paste the file into your project and it should just work.

AddonSK: how can window from require('sdk/window/utils').openDialog interact with main.js script?

I am writing a FF addon SDK plugin.
My main.js opens a popup window with :
var win = require('sdk/window/utils').openDialog({
url:"http://www.example.com/index.html",
features: Object.keys({
resizable: true,
scrollbars: true
}).join(),
name: "myWin"
});
I need the window to send data to the main.js (index.html is an external HTTP classical html/JS page)
1: add a content script.
using pageMod or tabs API, inject a js file in to the http://www.example.com/index.html page.
2: sending message from the content script, then add an message listener in the main.js
In main.js:
var tab = win.tabs[0]; // as it is a pop-up window I assume it has only one tab
// attach content script to tab and access worker object
var worker = tab.attach({
contentScriptFile: self.data.url('popup_window_script.js'), // script from file
contentScript: '' // and/or script from string
});
// listen to worker / popup_window_script.js
worker.port.on('msg_from_popup_window_script_js', function ( msg ) {
// your code
});
// speak to worker / popup_window_script.js
worker.port.emit('msg_from_main_js', 'your message');
In popup_window_script.js:
// listen (to main.js)
self.port.on('msg_from_main_js', function ( msg ) {
// your code
});
// speak (to main.js)
self.port.emit('msg_from_popup_window_script_js', 'your message');

Twitter Typeahead.js with Yahoo Finance in AJAX

I am trying to couple the new version of Typeahead.js and using it with JSON that needs to be pulled from AJAX and not from a JSON file like they have in their examples. I just can't get it to work, I don't want to cache the JSON result or anything, I want to pull it live from Yahoo.
My HTML input is <input type="text" id="symbol" name="symbol" autofocus autocomplete="off" placeholder="Symbol" onkeyup="onSymbolChange(this.value)" />
My AJAX/PHP file has this to retrieve data (this part work, I tested it with Firebug)
header('Content-type:text/html; charset=UTF-8;');
$action = (isset($_GET['action'])) ? $_GET['action'] : null;
$symbol = (isset($_GET['symbol'])) ? $_GET['symbol'] : null;
switch($action) {
case 'autocjson':
getYahooSymbolAutoComplete($symbol);
break;
}
function getYahooSymbolAutoCompleteJson($symbolChar) {
$data = #file_get_contents("http://d.yimg.com/aq/autoc?callback=YAHOO.util.ScriptNodeDataSource.callbacks&query=$symbolChar");
// parse yahoo data into a list of symbols
$result = [];
$json = json_decode(substr($data, strlen('YAHOO.util.ScriptNodeDataSource.callbacks('), -1));
foreach ($json->ResultSet->Result as $stock) {
$result[] = '('.$stock->symbol.') '.$stock->name;
}
echo json_encode(['symbols' => $result]);
}
The JS file (this is where I'm struggling)
function onSymbolChange(symbolChar) {
$.ajax({
url: 'yahoo_autocomplete_ajax.php',
type: 'GET',
dataType: 'json',
data: {
action: 'autocjson',
symbol: symbolChar
},
success: function(response) {
$('#symbol').typeahead({
name: 'symbol',
remote: response.symbols
});
}
});
}
I don't think that I'm suppose to attach a typeahead inside an AJAX success response, but I don't see much examples with AJAX (except for a previous version of typeahead)... I see the JSON response with Firebug after typing a character but the input doesn't react so good. Any guidance would really be appreciated, I'm working on a proof of concept at this point... It's also worth to know that I'm using AJAX because I am in HTTPS and using a direct http to Yahoo API is giving all kind of problems with Chrome and new Firefox for insecure page.
UPDATE
To make it to work, thanks to Hieu Nguyen, I had to modify the AJAX JSON response from this echo json_encode(['symbols' => $result]); to instead this echo json_encode($result); and modify the JS file to use the code as suggested here:
$('#symbol').typeahead({
name: 'symbol',
remote: 'yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY'
});
I have to do it in reverse, i.e: hook the ajax call inside typeahead remote handler. You can try:
$('#symbol').typeahead({
name: 'symbol',
remote: '/yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY'
});
You don't have to create onSymbolChange() function since typeahead will take care of that already.
You can also filter and debug the response from backend by using:
$('#symbol').typeahead({
name: 'symbol',
remote: {
url: '/yahoo_autocomplete_ajax.php?action=autocjson&symbol=%QUERY',
filter: function(resp) {
var dataset = [];
console.log(resp); // debug the response here
// do some filtering if needed with the response
return dataset;
}
}
});
Hope it helps!

Perl WWW:Mechanize::Firefox disable alert

I am using Perl's WWW::Mechanize::Firefox to automate some website interaction. I am stuck at dealing with the alert popup. Until I click it I cannot go to the next stage. I am looking for a way to either disable it or Click OK.
I have tried the following till now without any success:
$mech->eval("alert = function(val){console.log(val+' (alert disabled)');};");
$mech->eval("window.alert = function(val){console.log(val+' (alert disabled)');};");
$mech->eval_in_page("alert = function(val){console.log(val+' (alert disabled)');};");
$mech->eval_in_page('alert("Hello");', { alert => sub { print "Captured alert: '#_'\n" } });
$mech->eval_in_page('', { alert => sub { print "Captured alert: '#_'\n" } });
My test files are at:
https://github.com/jahagirdar/voter-reg/blob/master/bin/a.html
https://github.com/jahagirdar/voter-reg/blob/master/bin/fill_form.pl
I am using Firefox version 15.0.1 "Mozilla Firefox Ubuntu canonical version 1".
Installed addons are mozrepl 1.1 and Mozilla Ubuntu modifications 2.1.1.

Require.JS and JS Test Driver: Unexpected token <

I am trying to test a simple Backbone Model loaded via RequireJS:
define ["backbone"], (Backbone)->
class Todo extends Backbone.Model
defaults:
title: ''
priority: 0
done: false
validate: (attrs) ->
errs = {}
hasErrors = false
if (attrs.title is "")
hasErrors = true
errs.title = "Please specify a todo"
if hasErrors
return errs
toggleDone: ->
#save("done", !#get("done"))
return Todo
My tests look like:
requirejs.config
baseUrl: "js/"
paths:
jquery: "https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min"
jqueryui: "https://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min"
json2: "http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2"
underscore: "http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min"
backbone: "http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min"
backboneLocalStorage: "https://raw.github.com/jeromegn/Backbone.localStorage/master/backbone.localStorage-min"
shim:
"underscore":
exports: "_"
"backbone":
deps: ["jquery", "underscore", "json2"]
exports: "Backbone"
"jqueryui":
deps: ["jquery"]
"backboneLocalStorage":
deps: ["backbone"]
exports: "Backbone.LocalStorage"
require ["models/todo"], (Todo) ->
console.log Todo
TodoTests = TestCase("TodoTests")
TodoTests::testCreateTodo = ->
todo = new Todo({ title: "Hello" })
assertEquals "Hello", todo.get("title")
assertEquals 0, todo.get("priority")
assertEquals false, todo.get("done")
The JS Test Driver config:
server: http://localhost:3001
load:
- ../public/js/libs/require.js
- ../public/js/tests.js
serve:
- ../public/js/models/*
- ../public/js/collections/*
- ../public/js/views/*
Problem seen from the JS Test Driver listened page on Chrome console:
Uncaught SyntaxError: Unexpected token <
Looking at Todo.js from Chrome,
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><title>Console Runner</title><script type="text/javascript">var start = new Date().getTime();</script>
Uncaught SyntaxError: Unexpected token <
<script src="/static/lib/json2.js" type="text/javascript"></script><script src="/static/lib/json_sans_eval.js" type="text/javascript"></script><script src="/static/jstestdrivernamespace.js" type="text/javascript"></script><script src="/static/lib/jquery-min.js" type="text/javascript"></script><script src="/static/runner.js" type="text/javascript"></script><script type="text/javascript">jstestdriver.runConfig = {'debug':false};</script>
<script type="text/javascript">jstestdriver.console = new jstestdriver.Console();
Notice its a HTML page instead of my actual JS. Also console.log(Todo) returns undefined since a HTML page is returned in place of a JS. Did I configure this wrongly?
I struggled with this for days and searched with Google endlessly until I finally found out what JsTestDriver was doing. In your requirejs.config, for your tests to run properly, your baseUrl needs to be:
baseUrl: /test/path/to/your/stuff
The reason is when JsTestDriver makes its server, it prepends /test/ to all the directories. If the baseUrl isn't set properly, it starts trying to send back local a reference to window I think.
The only other problem that I see you may run into is running the tests with the require statement as the first line. I have Jasmine and putting my require() at the top of my tests caused them to never run so I had to do it in my beforeEach for my tests and get the object before they ran.
I'm probably doing something wrong though I see countless other people claiming that the require statement in Jasmine works, so my hunt continues.
Have you checked your ajax responses? I just had the same thing and that '<' was from the opening doctype tag that was returned when the resource 404'd...