I made a web application using Dancer2. I need to call an external program on an ajax request. The problem is that the request freezes until the program is finished.I tried a few things like using an & in the systemcall and fork, but it didn't work.
Here is the simplified code:
Perl:
package Test::App;
use Dancer2;
our $VERSION = '0.1';
get '/' => sub {
template 'index';
};
get '/startscript' => sub{
my $pid = fork();
$SIG{CHLD} = 'IGNORE';
if($pid == 0) {
`sleep 10`;
exit 0;
}
};
get '/gettest' => sub{
return "test"
};
true;
javascript:
$(document).ready(function() {
jQuery.get('/startscript', getSomething);
});
function getSomething(data){
jQuery.get('/gettest', getSomething);
console.log(data);
}
Old question, but the following has worked well for me - use & to have the shell run the command in the background. This returns control to the route handler immediately, and the command runs away in the background.
Perl:
post '/run-slow-script-in-background' => sub {
my $slow_command = q{/full/path/to/slow/script --arg1 what --arg2 ever &};
system($slow_command);
};
Related
I've created some tests in Cypress to add and duplicate article in our Angular application. The code for test ArticlesTest.js
describe('Shop articles single station', () => {
const productManagementPage = new ProductManagementPage()
const shopArticlesPage = new ShopArticlesPage()
before(() => {
var credentials =
{
"username": "someusername#user.name",
"password": "strongPassword!"
}
cy.navigateToProductManagement(credentials)
})
beforeEach(() => {
productManagementPage.shopArticlesProgrammingButton().click()
shopArticlesPage.WaitUntilPageLoaded('/productmanagement/api/v1/articles/get', 'GetArticles')
})
it('Add article', () => {
var randomNumber = RandomDataGenerator.GenerateRandomInt(1000,4999)
var randomName = RandomDataGenerator.GenerateRandomString(20)
var randomPrice = RandomDataGenerator.GenerateRandomDecimal(1,99)
shopArticlesPage.newArticleButton().click()
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
shopArticlesPage.deleteButton().should('be.disabled')
shopArticlesPage.articlesList().should('not.exist')
shopArticlesPage.articleNumberTextBox().should('be.enabled')
shopArticlesPage.articleNumberTextBox().type(randomNumber)
shopArticlesPage.articleNameTextBox().type(randomName)
shopArticlesPage.articleUnitPriceTextBox().type(randomPrice)
shopArticlesPage.undoButton().should('be.enabled')
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('exist')
shopArticlesPage.articlesList().should('exist')
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
})
it('Duplicate article', () => {
var articleNumber = RandomDataGenerator.GenerateRandomInt(51,65)
var newArticleNumber = RandomDataGenerator.GenerateRandomInt(1000, 4999)
var newArticleName = RandomDataGenerator.GenerateRandomString(20)
shopArticlesPage.articlesList().selectFromList(articleNumber)
const articleUnitPrice = shopArticlesPage.articleUnitPriceTextBox().invoke('text')
const vatCodeValue = shopArticlesPage.vatCodeDropDown().invoke('text')
const cardCodeValue = shopArticlesPage.cardCodeDropDown().invoke('text')
shopArticlesPage.duplicateArticleButton().click()
shopArticlesPage.WaitUntilPageLoaded()
shopArticlesPage.articleNumberTextBox().type(newArticleNumber)
shopArticlesPage.articleNameTextBox().type(newArticleName)
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('be.enabled')
})
WaitUntilPageLoaded() method code is:
WaitUntilPageLoaded(path, alias) {
return cy.waitForRequestToComplete(path, alias)
}
which, in turn, is custom Cypress command:
Cypress.Commands.add('waitForRequestToComplete', (path, alias) => {
cy.intercept('POST', path).as(alias)
cy.wait('#' + alias).its('response.statusCode').should('be.ok')
})
In 1st beforeEach() run, there's no problem with intercepting GetArticles and waiting for it to complete.
The problem starts in 2nd test, as it looks like GetArticles is not intercepted, it's not called at all, though it's supposed to be. The problem doesn't exist when clicking through the application manually, and /articles/get is always invoked.
The test ends up with error message
Timed out retrying after 30000ms: cy.wait() timed out waiting 30000ms for the 1st request to the route: GetArticles. No request ever occurred.
I've also tried using other endpoint e.g. vatcodes/get, and it works perfectly. The problem occurs only for articles/get, but I don't see any trail that would tell my why this happens for articles endpoint.
What is the problem? Why Cypress "blocks" 2nd call to this endpoint? What's more interesting, the problem doesn't exist for GetFeatures alias, which is created in an identical way.
Make sure the network intercept is registered before the application makes the call.
it('is registered too late', () => {
cy.intercept('/todos').as('todos')
cy.visit('/')
cy.wait('#todos')
})
In our case, we need to register the intercept before visiting the page. Once the page is loaded, the application fetches the todo items, and everything is working as expected.
you can see this link: https://glebbahmutov.com/blog/cypress-intercept-problems/
If I'm reading the situation correctly, the last log image is the failing test.
There is no (xhr) 200 /productmanagement/api/v1/articles/get showing there.
It goes straight from api/v1/subscriptionfeatures/get to api/v1/vatcodes/get, but in the first test the api/v1/articles/get was between those two calls.
If it occurs later in the screenshot, add an increased timeout to catch it (the same intercept can use the longer timeout in both tests, but it won't delay the first test).
This may mean you have found a bug in the app - it seems that a "Duplicate" action should have the same POSTs as an "Add" action.
Have you resolved this?
I'm using this config:
Given('a GraphQL service error is thrown', () => {
cy.intercept({ method: 'POST', url: '/uat/graphql', times: 1 }, { forceNetworkError: true });
});
With times: 1. But the interception does not block the request now.
I found times in the docs.
I'm trying to automate login into
https://strade.sharekhan.com
my $driver = Selenium::Remote::Driver->new;
$driver->get("https://strade.sharekhan.com/");
I'm able to successfully open the firefox browser and fetch the page.
But the input elements aren't visible.
my $page_source = $driver->get_page_source();
$driver->find_element('emailLoginId')->send_keys("abcdefg");
The login section seems to be inside a separate class item,whose html source appears in the browser debugger,but when trying via selenium,the class item is empty.
I only know basic Javascript/jQuery... kindly help me out,what is it that I'm missing
my $login_element = $driver->find_element_by_class('loginresponsive');
You can always wait for it to show up.
The following was written for Selenium::Chrome, but it demonstrates a portable principle.
use constant POLL_INTERVAL => 0.1;
use Time::HiRes qw( sleep time );
sub wait_for {
my ($xpath, $max_wait) = #_;
my $wait_until = time() + $max_wait;
while (1) {
if ( my $nodes = nf_find_elements($xpath) ) {
return wantarray ? #$nodes : $nodes->[0];
}
my $time_left = $wait_until - time();
return () if $time_left <= 0;
sleep(min($time_left, POLL_INTERVAL));
}
}
# Version of `find_elements` that doesn't die (non-fatal) when the element isn't found.
sub nf_find_elements {
my $nodes;
if (!eval {
$nodes = $web_driver->find_elements(#_);
return 1; # No exception.
}) {
return undef if $# =~ /Unable to locate element|An element could not be located on the page using the given search parameters/;
die($#);
}
return wantarray ? #$nodes : $nodes;
}
Example usage:
my $node = wait_for('//some/path', 4.0) # Wait up to 4s
or die("Login was unsuccessful.\n");
Time::HiRes's sleep doesn't get interrupted by signals, so I used the following to make my Ctrl-C handler responsive:
use Time::HiRes qw( );
use constant SLEEP_INTERVAL => 0.1;
# Hi-res sleep that gives signal handlers a chance to run.
use subs qw( sleep );
sub sleep {
if (!$_[0]) {
Time::HiRes::sleep(SLEEP_INTERVAL) while 1;
return; # Never reached.
}
my $sleep_until = time() + $_[0];
while (1) {
my $time_left = $sleep_until - Time::HiRes::time();
return if $time_left <= 0;
Time::HiRes::sleep(min($time_left, SLEEP_INTERVAL));
}
}
Make sure not to import sleep from Time::HiRes.
I tested this with Selenium::Chrome and it seems to work fine:
use strict;
use warnings;
use Selenium::Chrome;
# Enter your driver path here. See https://sites.google.com/a/chromium.org/chromedriver/
# for download instructions
my $driver_path = '/home/hakon/chromedriver/chromedriver';
my $driver = Selenium::Chrome->new( binary => $driver_path );
$driver->get("https://strade.sharekhan.com/");
$driver->find_element_by_name('emailLoginId')->send_keys("abcdefg");
sleep 45;
Screen shot taken while sleeping (see last line in code above):
I am trying to create a terminal app that will run indefinitely and will have the ability to read from the terminal.
I tried to user the "readline" api but the app terminates without waiting for any input.
I added a "while(true)" loop but it seems that the thread gets stacked in the loop and does not respond to my input.
I need a series of random numbers.
To accomplice it I added an interval of 1000ms and the result was the same with while loop.
To summary I need to create an app that reads from the terminal and create random numbers on a given interval.
Any guidance will be appreciated.
Edit 1
Additional information I just thought to give you.
I tried to put either the readline call or the interval in a separate forked process but nothing changed.
Also I tried to use recursion for the readline.
Edit 2
Although I accepted #amangpt777`s answer I would like to give another problem that you might encounter.
I was calling my script like this 'clear | node ./script.js' on windows` powershell.
I believe that it was the pipe that was blocking my input.
I don't know if this can happen on linux, I haven't tested it.
I just add it here so you keep it in mind.
I am not sure what you are trying to accomplish here. But following code will take input from user using readline and will keep on storing the input in an array. Note that I have some commented code in this which can be uncommented if you want a publish subscriber model. Also that you will need to add more code to sanitize and validate your input. I hope you will get some pointers to achieve what you want with this:
var readline = require('readline');
//var redis = require('redis');
//let subscriber = redis.createClient();
//let publisher = redis.createClient();
let numEntered = [];
var r1 = readline.createInterface(
{
"input": process.stdin,
"output": process.stdout
}
);
// subscriber.subscribe('myFunc');
// subscriber.on('message', (channel, msg) => {
// //Your logic
// });
function printMyArr(){
console.log("Numbers entered till now: ", numEntered);
}
function askNumber(){
askQuestion('Next Number?\n')
.then(ans => {
handleAnswer(ans);
})
.catch(err => {
console.log(err);
})
}
function handleAnswer(inputNumber) {
if(inputNumber === 'e') {
console.log('Exiting!');
r1.close();
process.exit();
}
else {
numEntered.push(parseInt(inputNumber));
//publisher.publish('myFunc', parseInt(inputNumber));
// OR
printMyArr();
askNumber();
}
}
function askQuestion(q) {
return new Promise((resolve, reject) => {
r1.question(q, (ans) => {
return resolve(ans);
});
});
}
function init() {
askQuestion('Enter Stream. Press e and enter to end input stream!\n')
.then(ans => {
handleAnswer(ans);
})
.catch(err => {
console.log(err);
})
}
init();
I'm using this Gumroad-API npm package in order to fetch data from an external service (Gumroad). Unfortunately, it seems to use a .then() construct which can get a little unwieldy as you will find out below:
This is my meteor method:
Meteor.methods({
fetchGumroadData: () => {
const Gumroad = Meteor.npmRequire('gumroad-api');
let gumroad = new Gumroad({ token: Meteor.settings.gumroadAccessKey });
let before = "2099-12-04";
let after = "2014-12-04";
let page = 1;
let sales = [];
// Recursively defined to continue fetching the next page if it exists
let doThisAfterResponse = (response) => {
sales.push(response.sales);
if (response.next_page_url) {
page = page + 1;
gumroad.listSales(after, before, page).then(doThisAfterResponse);
} else {
let finalArray = R.unnest(sales);
console.log('result array length: ' + finalArray.length);
Meteor.call('insertSales', finalArray);
console.log('FINISHED');
}
}
gumroad.listSales(after, before, page).then(doThisAfterResponse); // run
}
});
Since the NPM package exposes the Gumorad API using something like this:
gumroad.listSales(after, before, page).then(callback)
I decided to do it recursively in order to grab all pages of data.
Let me try to re-cap what is happening here:
The journey starts on the last line of the code shown above.
The initial page is fetched, and doThisAfterResponse() is run for the first time.
We first dump the returned data into our sales array, and then we check if the response has given us a link to the next page (as an indication as to whether or not we're on the final page).
If so, we increment our page count and we make the API call again with the same function to handle the response again.
If not, this means we're at our final page. Now it's time to format the data using R.unnest and finally insert the finalArray of data into our database.
But a funny thing happens here. The entire execution halts at the Meteor.call() and I don't even get an error output to the server logs.
I even tried switching out the Meteor.call() for a simple: Sales.insert({text: 'testing'}) but the exact same behaviour is observed.
What I really need to do is to fetch the information and then store it into the database on the server. How can I make that happen?
EDIT: Please also see this other (much more simplified) SO question I made:
Calling a Meteor Method inside a Promise Callback [Halting w/o Error]
I ended up ditching the NPM package and writing my own API call. I could never figure out how to make my call inside the .then(). Here's the code:
fetchGumroadData: () => {
let sales = [];
const fetchData = (page = 1) => {
let options = {
data: {
access_token: Meteor.settings.gumroadAccessKey,
before: '2099-12-04',
after: '2014-12-04',
page: page,
}
};
HTTP.call('GET', 'https://api.gumroad.com/v2/sales', options, (err,res) => {
if (err) { // API call failed
console.log(err);
throw err;
} else { // API call successful
sales.push(...res.data.sales);
res.data.next_page_url ? fetchData(page + 1) : Meteor.call('addSalesFromAPI', sales);
}
});
};
fetchData(); // run the function to fetch data recursively
}
I'm trying to extract metadata from video files by running ffprobe using nativeProcess. The code below works just as it should for one file, but causes an error when trying to loop through a series of files.
I know the cause of the problem is that Air tries to start a new nativeProcess before the old one is finished. I know it's something to do with listening to the air.NativeProcessExitEvent.EXIT. I just can't get it to work. Any suggestions would be greatly appreciated.
function fileOpen(){
var directory = air.File.userDirectory;
try
{
directory.browseForDirectory("Select Directory");
directory.addEventListener(air.Event.SELECT, directorySelected);
}
catch (error)
{
air.trace("Failed:", error.message)
}
function directorySelected(event)
{
directory = event.target ;
var files = directory.getDirectoryListing();
for(i=0; i < files.length; i++){
getMetadata(files[0].nativePath)
//wait here for nativeProcess to finish
}
}
}
function getMetadata(filePathIn){
if(air.NativeProcess.isSupported)
{
}
else
{
air.trace("NativeProcess not supported.");
}
fileIn = filePathIn.toString()
var nativeProcessStartupInfo = new air.NativeProcessStartupInfo();
var file = air.File.applicationStorageDirectory.resolvePath("ffprobe");
nativeProcessStartupInfo.executable = file;
args = new air.Vector["<String>"]();
args.push("-sexagesimal","-show_format","-loglevel","quiet","-show_streams","-print_format","json",filePathIn)
nativeProcessStartupInfo.arguments = args;
process = new air.NativeProcess();
process.start(nativeProcessStartupInfo);
process.addEventListener(air.ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
process.addEventListener(air.ProgressEvent.STANDARD_ERROR_DATA, onErrorData);
process.addEventListener(air.NativeProcessExitEvent.EXIT, onExit);
process.addEventListener(air.IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onIOError);
process.addEventListener(air.IOErrorEvent.STANDARD_ERROR_IO_ERROR, onIOError);
}
function onOutputData()
{
var fileMetadataJSON = process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable);
air.trace(fileMetadataJSON)
}
function onErrorData(event)
{
air.trace("ERROR -", process.standardError.readUTFBytes(process.standardError.bytesAvailable));
}
function onExit(event)
{
air.trace("Process exited with ", event.exitCode);
}
function onIOError(event)
{
air.trace(event.toString());
}
Here an outline that should give you an idea of what to do:
In your directorySelected() method, promote the local variable files (an Array) to a member variable. This will make the list of files that was selected available so that other methods can access it.
Remove the for loop in your directorySelected() method. Instead of running the loop and trying to run the native process here, call a new method, let's call it dequeueFileAndRunNativeProcess().
This new dequeueFileAndRunNativeProcess() method should check the length of the files array, and if it is greater than 0, it should pop() the first file in the files array and execute the native process on it.
In your onExit() method, call the dequeueFileAndRunNativeProcess() method again.
The idea here is to move the code that runs the native process into a method that you can trigger from two places: once immediately after the user finishes browsing for files, and then again after the native process finishes it's work. The process ends when the files array has no more files in it.