I was tempted to use jetty as a lightweight server to resolve the JVM-fork() memory problem as I already posted however nodejs provides a lighter and so from my simplistic design poit of view better alternative.
Below is the code for such a server:
#!/usr/local/bin/node
/*
** shell-server.js returns json response with the stdout and stderr of a shell command
**
**
** @Author: Nestor Urquiza
** @Date: 09/29/2011
**
*/
/*
* Dependencies
*/
var http = require('http'),
url = require('url'),
exec = require('child_process').exec;
/*
* Server Config
*/
var host = "127.0.0.1",
port = "8088",
thisServerUrl = "http://" + host + ":" + port;
/*
* Main
*/
http.createServer(function (req, res) {
req.addListener('end', function () {
});
var parsedUrl = url.parse(req.url, true);
var cmd = parsedUrl.query['cmd'];
var async = parsedUrl.query['async'];
res.writeHead(200, {'Content-Type': 'text/plain'});
if( cmd ) {
var child = exec(cmd, function (error, stdout, stderr) {
var result = '{"stdout":' + stdout + ',"stderr":"' + stderr + '","cmd":"' + cmd + '"}';
res.end(result + '\n');
});
} else {
var result = '{"stdout":"' + '' + '","stderr":"' + 'cmd is mandatory' + '","cmd":"' + cmd + '"}';
res.end(result + '\n');
}
if(async == "true") {
var result = '{"stdout":"async request' + '' + '","stderr":"' + '' + '","cmd":"' + cmd + '"}';
res.end(result + '\n');
}
}).listen(port, host);
console.log('Server running at ' + thisServerUrl );
Once the server is running you can hit the below URL to get a list of the users loged in a OSX/linux/unix box:
http://localhost:8088/?cmd=whoHere is how to run a sleep command and a touch command demonstrating the usage of async=true which basically will run the commands but will not check for stdout nor stderr. You can see the response comes back instantaneously however the file will be touched 5 seconds later:
$ ls --full-time /tmp/here
-rw-r--r-- 1 dev dev 0 2014-03-06 16:17:07.546398632 -0500 /tmp/here
$ date
Thu Mar 6 16:17:08 EST 2014
$ curl "http://localhost:8088/?cmd=sleep%2010;%20touch%20/tmp/here&async=true"
{"stdout":"async request","stderr":"","cmd":"sleep 10; touch /tmp/here"}
$ date
Thu Mar 6 16:17:12 EST 2014
$ ls --full-time /tmp/here
-rw-r--r-- 1 dev dev 0 2014-03-06 16:17:20.602399455 -0500 /tmp/here
Development environment
If you are running Windows you should download the node executable and run the server as:node c:\shell-server.js
If you are running OSX/Linux/Unix you have to install node make the script executable and run the below:
/path/to/shell-server.sh
Production deployment
You need to be sure the server runs as a daemon and that it restarts if it fails to serve. In debian/Ubuntu + monit you can follow these steps:$ sudo vi /opt/nodejs/shell-server.js
$ sudo vi /etc/init/shell-server.conf
#!/sbin/upstart
description "shell server runs a command specified by HTTP GET param 'cmd'"
author "admin"
start on startup
stop on shutdown
script
#export HOME="/root"
#exec sudo -u admin /usr/local/bin/node /opt/nodejs/shell-server.js
#exec su -c "/usr/local/bin/node /opt/nodejs/shell-server.js" dev
exec start-stop-daemon --start -c dev --exec /usr/local/bin/node /opt/nodejs/shell-server.js
end script
$ sudo vi /etc/monit/monitrc
...
#################################################################
# shell-server
################################################################
check host shell-server with address 127.0.0.1
start = "/sbin/start shell-server"
stop = "/sbin/stop shell-server"
if failed port 8088 protocol HTTP
request /
with timeout 10 seconds
then restart
group server
...
$ sudo monit reload
$ sudo vi /opt/nodejs/shell-server.js
$ wget http://localhost:8088 -O -
Note that upstart will log stdout and stderr to /var/log/upstart/shell-server.log
7 comments:
?cmd=cat /etc/passwd
?cmd=passwd //what happens?
?cmd=curl |sh
?cmd=rm -rf * ../ ../../
@Kenan it will happen the same as if you write e thise commands from Runtime.exec(). Security should be handled of course but that is out of tge scope of this post which is just about running commamds from Java without paying a high toll in memory consumption.
Very good, could you use crypto to encrypt and decrypt the commands?
@Ross the main motivation for this solution was to locally execute commands. Definitely support for SSL is never a bad idea even if running local commands so I would definitely vote for using crypto to support HTTPS instead of plain HTTP.
Great!! simple && powerful && controlrable!!
Instead of exec, you could also exec the commands over the same background process(es) for stateful behavior; see https://github.com/bitsofinfo/stateful-process-command-proxy
Post a Comment