Run Bash Script with Node from client request

Problem

I need to make a server-side script run when a user from the browser clicks a button...

I've been researching for a while, and can't figure it out.

What we have:

  • Node.js server (on localhost) running on Fedora Red Hat
  • NO PHP
  • Most pages are html + javascript + jQuery

To be more clear, here is what we'd like to happen:

-->User goes to http:// localhost /index.html

-->User selects colors, pushes "submit" button.

-->Selected colors go to the bash script (on the server) ./sendColors [listOfColors]

-->The bash script does it's thing.

================

Things I've tried

child_process.spawn

I WISH I could do this on the html page:

var spawn = require('child_process').spawn;
ls    = spawn(commandLine, [listOfColors]);

ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});

ls.stderr.on('data', function (data) {
  console.log('stderr: ' + data);
});

ls.on('close', function (code) {
  console.log('child process exited with code ' + code);
});

but this script is server-side, not client-side, so I can't run it on the html page (I believe). The error I get when I try to run this is that require is undefined.

browserify

I've tried installinst browserify, but the machine we are using isn't connected to the internet, and cannot use npm install. I've manually copied over the files to usr/lib and "required" it fine, but then it said that it couldn't find require "through", which is in browserify's index.js...

getRuntime

tried this thing:

        var bash_exit_code = 0;          // global to provide exit code from bash shell invocation

        function bash(command)
        {
          var c;           // a character of the shell's stdout stream
          var retval = "";          // the return value is the stdout of the shell

          var rt = Runtime.getRuntime();        // get current runTime object
          var shell = rt.exec("bash -c '" + command + "'");   // start the shell
          var shellIn = shell.getInputStream();        // this captures the output from the command

          while ((c = shellIn.read()) != -1)        // loop to capture shell's stdout
            {
              retval += String.fromCharCode(c);        // one character at a time
            }

          bash_exit_code = shell.waitFor();        // wait for the shell to finish and get the return code

          shellIn.close();          // close the shell's output stream

          return retval;
        }

said it didn't know what Runtime was

RequireJS

I've looked into RequireJS, but didn't understand how to use it in my case

eval

I've tried eval also... but I think that's for algebric expressions... didn't work.

ActiveX

even tried activeX:

variable=new ActiveXObject(...

said it didn't know what ActiveXObject is

================

Currently what I'm trying

HttpServer.js:

var http = require('http');
...
var colors = require('./colorsRequest.js').Request;
...

http.get('http://localhost/colorsRequest', function(req, res){
    // run your request.js script
    // when index.html makes the ajax call to www.yoursite.com/request, this runs
    // you can also require your request.js as a module (above) and call on that:
    res.send(colors.getList()); // try res.json() if getList() returns an object or array
    console.log("Got response ");// + res.statusCode);
    });

colorsRequest.js

var RequestClass = function() {
    console.log("HELLO");
}; 

// now expose with module.exports:
exports.Request = RequestClass;

index.html

...
var colorsList = ...
...
$.get('http://localhost/colorsRequest', function(colors) {
            $('#response').html(colorsList); // show the list
    });

I'm getting

GET http://localhost/colorsRequest 404 (Not Found)

Anyone got any ideas?

Problem courtesy of: Kayvar

Solution

Here's a simple boilerplate for the server (which uses Express, so you might need to install that first: npm install express):

var spawn   = require('child_process').spawn;
var express = require('express');
var app     = express();

app.use(express.static(__dirname));

app.get('/colorsRequest', function(req, res) {
  var command = spawn(__dirname + '/run.sh', [ req.query.color || '' ]);
  var output  = [];

  command.stdout.on('data', function(chunk) {
    output.push(chunk);
  }); 

  command.on('close', function(code) {
    if (code === 0)
      res.send(Buffer.concat(output));
    else
      res.send(500); // when the script fails, generate a Server Error HTTP response
  });
});

app.listen(3000);

You can pass it a color, and it will run the shellscript run.sh (of which it assumes is located in the same directory as the server JS file) with the color passed as argument:

curl -i localhost:3000/colorsRequest?color=green
# this runs './run.sh green' on the server

Here's a boilerplate HTML page (save it as index.html, put it in the same directory as the server code and the shell script, start the server, and open http://localhost:3000 in your browser):

<!doctype html>
<html>
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  </head>
  <body>
    <select>
      <optgroup label="Pick a color:">
        <option>green</option>
        <option>blue</option>
        <option>yellow</option>
        <option>orange</option>
      </optgroup>
    </select>
    <script>
      $('select').on('change', function() {
        $.get('/colorsRequest', { color : $(this).val() });
      });
    </script>
  </body>
</html>
Solution courtesy of: robertklep

Discussion

You are on the right way with your first approach, the child_process.spawn variant. Ofcourse you can't put this in the HTML page, as it is then executed in the browser, not in the server, but you can easily create a request in the browser (AJAX or page load, depending on what you need), that triggers running this script in the server.

Discussion courtesy of: Eugen Rieck

This recipe can be found in it's original form on Stack Over Flow.