Node.js running a shell command from the same process - javascript

I'm trying to make a way to boot up a Minecraft server from nodejs, but I'm having trouble making a way to run commands from nodejs.
const { spawn } = require('node:child_process')
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require('fs');
app.get('/start', (req, res) => {
fs.writeFile('minecraftstatus.txt', 'on', (err) => {
if (err) throw err;
});
const command = spawn('java', ['-jar', '-Xms2048M','-Xmx2048M', '-Dfile.encoding=utf8', 'server.jar', 'nogui'])
// the `data` event is fired every time data is
// output from the command
command.stdout.on('data', output => {
// the output data is captured and printed in the callback
fs.appendFile('console.txt', ("\n" + output.toString()), 'utf-8', err => {
console.log(err)
})
console.log("Output: ", output.toString())
})
res.status(200).send("OK")
});
app.listen(80, () => {
console.log('Server started on port 80');
});
From what you see above, whenever a user sends a GET request, it sends a command and appends any output to a text file. I need to make a way in order to send commands to Minecraft. I need to send commands to the same shell that nodejs ran the command.
I've tried this:
app.get('/mcstop', (req, res) => {
try {
const command2 = spawn('/stop')
// the `data` event is fired every time data is
// output from the command
command2.stdout.on('data', output => {
// the output data is captured and printed in the callback
console.log("Output: ", output.toString())
})
res.status(200).send("OK")
}
catch {
console.log("Oh no...")
}
});
Where it sends /stop to the shell, but it seems like it isn't being ran on the same shell as where the Minecraft server was created from.
How could I achieve this?

Related

My JSON.parse in app.js doesnt function properly

app.js:
const express = require("express");
const https = require("https");
const app = express();
const port = 3000;
app.get("/",function(req,res){
const url ="https://maps.googleapis.com/maps/api/geocode/jsonaddress=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY;
console.log(response.statusCode);
response.on("data",function(data){
var jatin=JSON.parse(data);
console.log(jatin);
})
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Error on Console
app.listen(3000,function(){ console.log("server started on port
3000"); })
server started on port 3000 200 undefined:26
"long_name"
SyntaxError: Unexpected end of JSON input
at JSON.parse ()
at IncomingMessage. (/home/jatin/Downloads/full_stack/Web-Development/maps/app.js:11:21)
at IncomingMessage.emit (events.js:189:13)
at IncomingMessage.Readable.read (_stream_readable.js:487:10)
at flow (_stream_readable.js:931:34)
at resume_ (_stream_readable.js:912:3)
at process._tickCallback (internal/process/next_tick.js:63:19) [nodemon] app crashed - waiting for file changes before starting
The output is visible when I run it on browser but on the console it throws an error.
For some reason JSON.parse() isn't working as expected.
I am trying to make a geocoding API call and in response, it gives me a JSON output...
which when I enter it as a URL on the browser the expected output is received
But when app.js is run on a node express server and when I hit my localhost:3000 I am getting the console error
Apparently the JSON.parse("data") is working but stops unexpectedly. Which leads to error.
You need to have your code to perform JSON.parse on end event instead, like this example form the documentation:
http.get('http://nodejs.org/dist/index.json', (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
Notice that this is http.get, but it should be the same for https.get, so in your code the on('data') should be used to assemble the chunks before your perform JSON.parse() on it.
const express = require("express");
const https = require("https");
const app = express();
const port = 3000;
app.get("/", function(req, res) {
const url = "API_URL";
https.get(url, function(response) {
console.log(response.statusCode);
let body = "";
response.on("data", function(data) {
body += data;
});
response.on("end", function() {
console.log(JSON.parse(body));
});
});
res.end("End data response");
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
basically .on('end', callback') and .on('data', callback') are event listener to data receive and request end events, and to be able to handle your requests response in node when using http(s).get you have to attache an event listener on data event which is invoked every time your request receives a chunk of the request response, and once the request ended by the service the event end will be invoked stating that there is no more data from the server requested hence the request ended.
as stated in the documentation:
The callback must take care to consume the response data for reasons
stated in http.ClientRequest section.

Merge Two codes

I have 2 files in Node js .I want to merge these 2, but I am facing problem..
This file calls function from python file
const app = express()
let runPy = new Promise(function(success, nosuccess) {
const { spawn } = require('child_process');
const pyprog = spawn('python', ['./ml.py']);
pyprog.stdout.on('data', function(data) {
success(data);
});
pyprog.stderr.on('data', (data) => {
nosuccess(data);
});
});
app.get('/', (req, res) => {
res.write('welcome\n');
runPy.then(function(testMLFunction) {
console.log(testMLFunction.toString());
res.end(testMLFunction);
});
})
app.listen(4000, () => console.log('Application listening on port 4000!'))
python file ml.py
def testMLFunction():
return "hello from Python"
print(testMLFunction())
Below file works on button click with post method
var fs = require('fs');
var server = http.createServer(function (req, res) {
if (req.method === "GET") {
res.writeHead(200, { "Content-Type": "text/html" });
fs.createReadStream("./form.html", "UTF-8").pipe(res);
} else if (req.method === "POST") {
var result = "";
req.on("data", function (chunk) {
console.log(chunk.toString());
result = chunk;
//body=body.toUpperCase;
});
req.on("end", function(){
res.writeHead(200, { "Content-Type": "text/html" });
res.end(result);
});
}
}).listen(3000);
how can I do that..
There are several things wrong here. I will explain as plain as possible.
You forgot to add in your code var express = require('express')
The promise you made, runPy, must be wrapped in a function, whereas your approach will instantly start the promise upon loading the script itself.
You are resolving/rejecting on first incoming output, you shouldn't do that because you won't be able to know what really happened in the shell. You need to store those output lines, this is the only way of you knowing what the script tells you.
In runPy you must resolve/reject upon pyprogr close event.
You cannot access directly the method of another script, no matter what that kind of file that is a py, sh, bat, js. However, you can access internal functions of it by passing arguments to the shell, and of course, that script must have the logic required to deal with those arguments.
When using spawn/exec you must keep in mind that YOU ARE NOT the user executing the script, the node user is, so different outcomes may occur.
Most importantly, your targeted script must PRINT/ECHO to shell, no returns! The best approach would be to print some json string, and parse it in javascript after the shell is closed, so you can have access to an object instead of a string.
Below you will find a demo for your use case, i changed the python file so it can print something.
ml.py
print('I\'m the output from ml.py')
index.js
const express = require('express');
const app = express()
let runPy = function () { // the promise is now wrapped in a function so it won't trigger on script load
return new Promise(function (success, nosuccess) {
const {spawn} = require('child_process');
const pyprog = spawn('python', ['./ml.py'], {shell: true}); // add shell:true so node will spawn it with your system shell.
let storeLines = []; // store the printed rows from the script
let storeErrors = []; // store errors occurred
pyprog.stdout.on('data', function (data) {
storeLines.push(data);
});
pyprog.stderr.on('data', (data) => {
storeErrors.push(data);
});
pyprog.on('close', () => {
// if we have errors will reject the promise and we'll catch it later
if (storeErrors.length) {
nosuccess(new Error(Buffer.concat(storeErrors).toString()));
} else {
success(storeLines);
}
})
})
};
let path = require('path');
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // you need to set this so you can catch POST requests
app.all('/', (req, res) => { // i've change this from .get to .all so you can catch both get and post requests here
console.log('post params', req.body);
if(req.body.hasOwnProperty('btn-send')){
runPy()
.then(function (pyOutputBuffer) {
let message = 'You sent this params:\n' +JSON.stringify(req.body, null,2) + '\n';
message += Buffer.concat(pyOutputBuffer).toString();
res.end(message);
})
.catch(console.log)
}else{
res.sendFile(path.join(__dirname,'form.html')); // you need an absolute path to 'file.html'
}
});
app.listen(4000, () => console.log('Application listening on port 4000!'));
form.html
<div>hello there</div>
<form action="/" method="post">
<input type="text" value="" name="some-text"/>
<button type="submit" value="1" name="btn-send" >Press me!</button>
</form>

Parse request in my simple Node Js server

I'm new to Node and am trying to build a simple server in Node using Express. The requests are in the form of say /input00001/1/output00001. What I need to do is to parse this request and if the flag is 1 (middle value), I need to replace the file \home\inputfiles\input00001.txt with file \home\outputfiles\output00001.txt. How is it possible to do that?
Here is my simple server so far. I'm OK with not using the Express and pure NodeJs if that makes things easier.
const express = require('express');
const app = express();
const port = 8000;
app.get('/', (request, response) => {
response.send('Hello from Express!');
request.param
});
app.get('/*', (request, response) => {
response.send('Start!');
var url = request.originalUrl;
});
app.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err);
}
console.log(`server is listening on ${port} for incoming messages`);
});
You should set up a route that expects these items as url parameters and then use those parameters to do what you want. For example if you're url is /input00001/1/output00001 then you could set up a route like this:
app.get('/:input/:flag/:output', (req, res) => {
var params = req.params
var input = params.input //input0001
var flag = params.flag // 1
var output = params.output //output0001
// now do what you need to with input, flag, and output
if(typeof flag!=='undefined' && flag==1){
var file_name_string = '\home\inputfiles\input00001.txt';
var res = file_name_string.replace("input", "output");
}
console.log(input, flag, output)
res.send("done")
})

Node JS Read display Console output on user's device

Is it possible to display the default console output from a node.js Server to the users client? I've tried several plugins like e.g. 'console.re' but not one of them worked for me.
I like to read the 'console.log' output in real time and display all output to a client (mobile device).
Thanks
console.log writes to stdout, so you can listen to it and do whatever with the data.
console.log([data,][args])
Prints to stdout with newline.
https://nodejs.org/api/console.html
Try advancing this code:
const spawn = require('child_process').spawn
const ls = spawn('ls', ['-lh', '/usr'])
const collectedData = []
ls.stdout.on('data', (data) => {
console.log('stdout: ' + data.toString())
collectedData.push({ event: 'CONSOLE_LOG', data })
// now just make the client consume collectedData in real-time
// or on refresh
})
ls.stderr.on('data', (data) => {
console.log('stderr: ' + data.toString())
})
ls.on('exit', (code) => {
console.log('child process exited with code ' + code.toString())
})
Child processes are built in. Take a sample through these docs here:
https://nodejs.org/api/child_process.html
I don't know anything about your setup, but if you want super easy in Express
app.get('/logging', (req, res) => {
res.send(collectedData)
})
Make the client hit it every 2000ms or something.
Otherwise, you need to create a socket with the server and push updates to the client, and handle them in the client.
http://www.socket.io
You can also setup TCP and UDP sockets fairly quick in node.js. Do some Googling.
I have some sample code I can give you:
/**
* SERVER
*/
// Load dgram module
const dgram = require('dgram')
// Create new socket
const server = dgram.createSocket('udp4')
// Listening event
server.on('listening', () => console.log('UDP Server listening'))
// Message event
server.on('message', (msg, rinfo) => {
console.log(`${rinfo.address}:${rinfo.port} - ${msg}`)
});
// Start UDP socket listener
const PORT = 3333
const HOST = '127.0.0.1'
server.bind(PORT, HOST)
/**
* CLIENT
*/
const client = dgram.createSocket('udp4')
const ip = require('ip')
client.send('Sample message', PORT, HOST, (err) => {
if (err) throw err
console.log(`UDP message sent: ${ip.address()}`)
client.close()
})

How to stop webdriver without crashing node js

is it possible to stop the selenium webdriver without stopping node?
I have following issue:
I try to create a API tool that does some web automation when getting a get request. So I am basically running selenium webdriver on a get request to /start via Express. I want the tool to check for different elements and when it somehow fails I want it to stop selenium but NOT the node instance (since a new get request could be send).
This is my code so far:
"use strict";
const webdriver = require('selenium-webdriver'),
Express = require('express'),
By = webdriver.By,
until = webdriver.until,
Keys = webdriver.Key,
app = new Express();
app.get("/automate", (req, res) => {
start(res);
});
function start(res) {
const driver = new webdriver.Builder().forBrowser('chrome').build();
driver.get('https://www.google.com/');
// # --- foo --- #
let errMessage = {pos: "FOO", message: "Ooops friendly robot has some troubles!"}
checkStep("#foo", errMessage);
driver.findElement(By.id("foo"))
.sendKeys("fooz");
// # --- bar --- #
errMessage = {pos: "BAR", message: "Ooops friendly robot has some troubles!"}
checkStep("#bar", errMessage);
driver.findElement(By.id("bar"))
.sendKeys("baz");
// etc…
function checkStep(selector, errMessage) {
driver.findElement(By.css(selector))
.then(() => {
console.log(`${selector} => found`);
})
.catch(err => {
console.log(`Error: ${err}`);
res.send(errMessage);
driver.quit();
});
}
}
app.get("*", (req, res) => {
res.send("Hello World");
});
// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
app.listen(port, err => {
if (err) { return console.error(err); }
console.info(`Server running on http://localhost:${port} [${env}]`);
});
it is actually working so far that when selenium do not find the element the response from the API is correct. In Selenium I get back:
{
"pos": "FOO",
"message": "Ooops friendly robot has some troubles!"
}
So far all good.
BUT unfortunately stopping selenium is also stopping Node from running.
The error I get is following:
throw error;
^
WebDriverError: no such session
(Driver info: chromedriver=2.30.477690 (c53f4ad87510ee97b5c3425a14c0e79780cdf262),platform=Ma
c OS X 10.12.5 x86_64)
at WebDriverError
Please help, thank you!
ps: I am not using webdriverio as you can see I use this package: https://www.npmjs.com/package/selenium-webdriver
Ok, I got this working. It’s a bit difficult solution but it works:
Using Child Processes
Basically, every time the app gets a get request to /automate it will now create a child process in node which runs the selenium scripts (a child process is kind of like using another thread. Here is a very good tutorial on child processes):
index.js
"use strict";
const Express = require('express');
const { spawn } = require('child_process');
const data = require('./data.json');
const app = new Express();
app.get("/automate", (req, res) => {
const child = spawn(
process.execPath,
[`${__dirname}/test.js`, JSON.stringify(data)],
{ stdio: ['inherit', 'inherit', 'inherit', 'pipe'] }
);
child.stdio[3].on('data', data => {
const response = JSON.parse(data.toString());
res.send(response);
console.log(response);
child.kill();
});
});
app.get("*", (req, res) => {
res.send("Hello World");
});
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
app.listen(port, err => {
if (err) { return console.error(err); }
console.info(`Server running on http://localhost:${port} [${env}]`);
});
test.js
"use strict";
// hook with argument 3, that is "pipe" from parent
const Net = require('net'),
pipe = new Net.Socket({ fd: 3 });
const data = JSON.parse(process.argv[2]);
const webdriver = require('selenium-webdriver'),
By = webdriver.By,
until = webdriver.until,
Keys = webdriver.Key;
function start() {
const driver = new webdriver.Builder().forBrowser('chrome').build();
driver.get('https://www.google.com/');
// # --- foo --- #
let errMessage = {pos: "lst-ib", message: "Ooops friendly robot has some troubles!"}
checkStep("#lst-ib")
.sendKeys("fooz");
driver.get('https://www.facebook.com/');
driver.get('https://www.google.com/');
driver.get('https://www.facebook.com/');
// # --- bar --- #
errMessage = {pos: "BAR", message: "Ooops friendly robot has some troubles!"}
checkStep("#bar")
.sendKeys("baz");
function checkStep(selector) {
driver.findElement(By.css(selector))
.then(() => {
console.log(`${selector} => found`);
})
.catch(err => {
console.log(`${selector} => not found`);
publish(errMessage);
driver.quit();
});
}
}
function publish(message) {
pipe.write(JSON.stringify(message));
}
start();
It is working like a charm: on each request opening a new child process and killing that child process if it sends some message while also responding with the message to the client. Like this you can easily have several selenium instances simultaneously.
You’re welcome.
ps: If you hate all this asyncron stuff from Selenium webdriver-sync seems like a good choice. It basically wraps the selenium code to be syncon instead of asyncron. Like that I am able to use try {} catch {} and to driver.quit(); without any errors for code that comes later. (But this comes with a disadvantage: it is actually blocking your other nodejs code.)

Categories