0

I have created a node endpoint to create rasterised version for my svg charts.

app.post('/dxexport', function(req, res){
    node2Phantom.createPhantomProcess(req,res);
});

My node to phantom function uses spawn to run phantomjs

var spawn = require('child_process').spawn;
exports.createPhantomProcess = function(req,res){

    var userRequest = JSON.stringify(req.body);
    var bin = "node_modules/.bin/phantomjs"
    var args = ['./dxexport/exporter-server.js', userRequest, res];
    var cspr = spawn(bin, args);


    cspr.stdout.on('data', function (data) {
        var buff = new Buffer(data);
        res.send(data);
    });

    cspr.stderr.on('data', function (data) {
        data += '';
        console.log(data.replace("\n", "\nstderr: "));
    });

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

};

when rendering is completed and file is successfully created I call the renderCompleted function inside phantomjs:

var renderCompleted = function (parameters) {
    var exportFile = fileSystem.open(parameters.exportFileName, "rb"),
        exportFileContent = exportFile.read();
    parameters.response.statusCode = 200;
    parameters.response.headers = {
        "Access-Control-Allow-Origin": parameters.url,
        "Content-Type": contentTypes[parameters.format],
        "Content-Disposition": "attachment; fileName=" + parameters.fileName + "." + parameters.format,
        "Content-Length": exportFileContent.length
    };
    parameters.response.setEncoding("binary");
    parameters.response.write(exportFileContent);

    /////// somehow send exportFileContent as node res object for download \\\\\\\\
    exportFile.close();
    parameters.format !== "svg" && fileSystem.remove(parameters.exportFileName);
    for (var i = 0; i < parameters.filesPath.length; i++)
        fileSystem.remove(parameters.filesPath[i]);
    parameters.filesPath = [];
    parameters.response.close()
};

this response is passed from nodejs however apparently this code is calling phantomjs methods so I get errors like

TypeError: 'undefined' is not a function (evaluating 'parameters.response.setEncoding("binary")')

How can I send the binary file response somehow to the node function so it can be sent with my node server to the user?

Any help is appreciated.

makeitmorehuman
  • 11,287
  • 3
  • 52
  • 76
  • Similar question but also unanswered: http://stackoverflow.com/questions/22919485/how-to-read-an-image-from-phantomjs-stdout-in-nodejs-to-serve-it – makeitmorehuman Aug 11 '15 at 11:38
  • 1
    Can't you just pass the file path and handle the file content in node.js? – Artjom B. Aug 11 '15 at 12:24
  • Just trying to do that. Thank you, yes it seems to be a better option. Phantom to make the file and node will read and send it. Will post the code if it worked – makeitmorehuman Aug 11 '15 at 12:25
  • @ArtjomB. I was wondering what you think about using a console.log statement (in my answer) to send the image file information back to node for rendering? I somehow think it's not elegant – makeitmorehuman Aug 11 '15 at 19:55

1 Answers1

0

Ok after some struggle here is the working solution if someone stumbles on this post.

As Artjom B. mentioned I found that the easiest way was delegate the rendering and file creation of the visualisation to phantomjs. Then send all the parameters related to those operations once done through the console.

Also updated the answer based on @ArtjomB.'s advice to wrap the console message sent in a unique beginning and end string so the risk of the other possible future outputs being mistaken for the intended rendered file object is mitigated.

var renderCompleted = function (parameters) {
    console.log("STRTORNDRD" + JSON.stringify(parameters) + "ENDORNDRD");
};

This then gets picked up by stdout and usable like this:

exports.exportVisual = function (req, res) {

    var userRequest = JSON.stringify(req.body);
    var bin = "node_modules/.bin/phantomjs"
    var args = ['./dxexport/exporter-server.js', userRequest, res];
    var cspr = spawn(bin, args);
    var contentTypes = {
        pdf: "application/pdf",
        svg: "image/svg+xml",
        png: "image/png"
    };


    cspr.stdout.on('data', function (data) {

        var buff = new Buffer(data).toString('utf8');
        var strData = buff.match(new RegExp("STRTORNDRD" + "(.*)" + "ENDORNDRD"));

        if (strData) {

            var parameters = JSON.parse(strData[1]);

            var img = fs.readFileSync(parameters.exportFileName);
            var headers = {
                "Access-Control-Allow-Origin": parameters.url,
                "Content-Type": contentTypes[parameters.format],
                "Content-Disposition": "attachment; fileName=" + parameters.fileName + "." + parameters.format,
                "Content-Length": img.length
            };

            res.writeHead(200, headers);
            res.end(img, 'binary');

            // delete files after done
            if (parameters.format != "svg") {
                fs.unlink(parameters.exportFileName);
            }

            for (var i = 0; i < parameters.filesPath.length; i++)
                fs.unlink(parameters.filesPath[i]);

            // done. kill it
            cspr.kill('SIGINT');

        }

    });

    cspr.stderr.on('data', function (data) {
        data += '';
        console.log(data.replace("\n", "\nstderr: "));
    });

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

};
makeitmorehuman
  • 11,287
  • 3
  • 52
  • 76
  • 1
    You can't be always too sure about the cleanness of the PhantomJS console output. If there is even one line that is printed additionally, you won't be able to parse the JSON if you take the full output. You probably should print the JSON to the console with some unique prefix and iterate over all lines in node to find that one line, remove the unique prefix and parse the remaining JSON as you do now. – Artjom B. Aug 11 '15 at 20:28