title: How to control (start, abort) an external program via a HTML page using ExpressJS?
body: <p>I'm trying to build a web-based 'configuration wizard' that asks the user some values, renders them into a config file and then lets the user start a server-side program using this config. </p>
  
  <p>I chose ExpressJS, since it seemed to be relatively light-weight and easy to learn. However, I have no experience with web programming or JavaScript whatsoever.</p>
  
  <p>Somehow I managed to build the wizard pages, although it feels like I'm using something horribly wrong (the application is basically stateless and drags the user values through different HTML pages by rendering the previous values into hidden fields in the new page's form ...).</p>
  
  <p>After all the wizard pages, there comes a 'control' page, where the user can see (and edit) the computed configuration. The page has two buttons:</p>
  
  <p>One runs a program to validate the configuration and check for errors. This is done via <code>child_process.execSync(...)</code> (since it doesn't take long) and works quite well.</p>
  
  <p>The other one is supposed to start a long-running task in the background. While the task is running, buttons on the page should be disabled, except for an 'Abort' button, which should kill the background task (not yet implemented). After the task is finished, the page should be reloaded with enabled buttons and a status field containing whether the task was successful. Unfortunately, I can't get this to work.</p>
  
  <p>I'm using ExpressJS with pug.</p>
  
  <p>control.js:</p>
  
  <pre><code>var express = require('express');
  var router = express.Router();
  var utils = require('./utils');
  var createError = require('http-errors');
  var child_process = require('child_process');
  
  router.all('/', function(req, res,next) {
    var status = null;
    if ([req.app.locals.buttons.doStuff].includes(req.body.submit)) {
      next();
    }
    switch(req.body.submit) {
      case req.app.locals.buttons.validate:
        try {
          utils.validate(req.body.config_text); // helper function that basically calls an external program via child_process.execSync
          status = "config.yaml is valid!"
        } catch (err) {
          status = "config.yaml is invalid: " + err
        }
        break;
      case req.app.locals.buttons.doStuff:
        res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status, disabled: true });
        return;
    }
    res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
  });
  
  router.post('/', function(req, res, next) {
    var status = null;
    console.log("Entering command mode: " + req.body.submit);
    switch(req.body.submit) {
      case req.app.locals.buttons.doStuff:
        status = "Doing stuff ... this will take a while (timeout after 1h)."
        console.log(status);
        child_process.exec("doStuff arg1 arg2 arg3", {cwd: req.app.locals.dostuff_directory, timeout: 3600000}, function(err, stdout, stderr) {
          status = "doStuff successfully finished!"
          if (err) {
            status = "Error while doing stuff: \n" + stderr;
          }
          res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
        });
        break;
      default:
        status = "Unknown command: " + req.body.submit;
        res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
        break;
    }
  });
  
  module.exports = router;
  </code></pre>
  
  <p>and the corresponding control.pug:</p>
  
  <pre><code>extends layout // basically just places the content block into &lt;body&gt;
  
  block content
    h1= title
    h3 Control Page
  
    form(method="post", autocomplete="off")
      label(for="config_text") config.yaml
      textarea(name="config_text", cols="70", rows="35")
        = config_data
      pre
        code(name="status")
          = status
      div(class="controls")
        input(type="submit", name="submit", value=buttons.validate, disabled=disabled)
        input(type="submit", name="submit", value=buttons.doStuff, disabled=disabled)
        if (disabled)
          input(type="submit", name="submit", value=buttons.abort)
  </code></pre>
  
  <p>If I click the 'doStuff' button, the buttons are disabled, but the task doesn't run and I get <code>error: Cannot set headers after they are sent to the client</code>.</p>
  
  <p>I guess I could use <code>child_process.execSync</code> as a workaround instead, since that seems to work, but I assume that it will freeze the page while the task is running. I also have the feeling I should use a different method to react to button clicks than HTML <code>form</code> tags.</p>
  
  <p>How can I achieve the desired behaviour? </p>