diff --git a/README.md b/README.md index a7f9cec..e8637db 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,24 @@ wkhtmltopdf('http://apple.com/', { output: 'out.pdf', ignore: ['QFont::setPixelSize: Pixel size <= 0 (0)'] }); + // RegExp also acceptable wkhtmltopdf('http://apple.com/', { output: 'out.pdf', ignore: [/QFont::setPixelSize/] }); + +// Using as express middlware +function exampleMiddleware (req, res, next) { + wkhtmltopdf(('
Hello express
', { timeout: 30 }, function (err, stream) { + if (err) { + return next(err); + } + res.attachment(`out.pdf`); + stream.pipe(res); + }) +} + ``` `wkhtmltopdf` is just a function, which you call with either a URL or an inline HTML string, and it returns @@ -72,12 +85,14 @@ a stream that you can read from or pipe to wherever you like (e.g. a file, or an ## Options There are [many options](http://wkhtmltopdf.org/docs.html) available to -wkhtmltopdf. All of the command line options are supported as documented on the page linked to above. The +wkhtmltopdf. All of the command line options are supported as documented on the page linked to above. The options are camelCased instead-of-dashed as in the command line tool. Note that options that do not have values, must be specified as a boolean, e.g. **debugJavascript: true** There is also an `output` option that can be used to write the output directly to a filename, instead of returning a stream. +To interrupt a long running wkhtmltopdf process use `timeout` option with a value of seconds to wait for regular termination. Any falsy value means "wait as long as possible", that's default. + ### Debug Options Apart from the **debugJavascript** option from wkhtmltopdf, there is an additional options **debug** and **debugStdOut** which will help you debug rendering issues, by outputting data to the console. **debug** prints and **stderr** messages while **debugStdOut** prints any **stdout** warning messages. diff --git a/index.js b/index.js index e1471be..5e16b58 100644 --- a/index.js +++ b/index.js @@ -57,7 +57,7 @@ function wkhtmltopdf(input, options, callback) { keys.forEach(function(key) { var val = options[key]; - if (key === 'ignore' || key === 'debug' || key === 'debugStdOut') { // skip adding the ignore/debug keys + if (key === 'ignore' || key === 'debug' || key === 'debugStdOut' || key === 'timeout') { // skip adding the ignore/debug keys return false; } @@ -124,14 +124,24 @@ function wkhtmltopdf(input, options, callback) { var child = spawn(wkhtmltopdf.shell, ['-c', 'set -o pipefail ; ' + args.join(' ') + ' | cat'], spawnOptions); } + var timeout + if (options.timeout) { + timeout = setTimeout( function () { + var timeoutError = new Error('Child process terminated due to timeout'); + timeoutError.code = '_EXIT_TIMEOUT'; + handleError(timeoutError); + }, options.timeout*1000); + } + var stream = child.stdout; // call the callback with null error when the process exits successfully child.on('exit', function(code) { if (code !== 0) { - stderrMessages.push('wkhtmltopdf exited with code ' + code); + stderrMessages.code = code; handleError(stderrMessages); } else if (callback) { + clearTimeout(timeout); callback(null, stream); // stream is child.stdout } }); @@ -140,12 +150,26 @@ function wkhtmltopdf(input, options, callback) { var stderrMessages = []; function handleError(err) { var errObj = null; + var code; + var parallelError; if (Array.isArray(err)) { + code = err.code; + parallelError = err.parallelError; + // as `err` could contain a small chunks of stdout (and it does sometimes in Windows) + // we have to concatenate it before using + err = Buffer.concat(err).toString(); + var lines = err.split(/[\r\n]+/) + .map(function(line) { + return line.trim(); + }) + .filter(function(line) { + return !!line; + }) // check ignore warnings array before killing child if (options.ignore && options.ignore instanceof Array) { var ignoreError = false; options.ignore.forEach(function(opt) { - err.forEach(function(error) { + lines.forEach(function(error) { if (typeof opt === 'string' && opt === error) { ignoreError = true; } @@ -158,10 +182,19 @@ function wkhtmltopdf(input, options, callback) { return true; } } - errObj = new Error(err.join('\n')); + errObj = new Error(lines[0] || ('Child process finished with exit code ' + code)); + errObj.code = 'WKHTMLTOPDF_EXIT_ERROR'; + errObj.errno = code; + errObj.details = err; + errObj.parallelError = parallelError; + } else if (err instanceof Error) { + errObj = err; } else if (err) { - errObj = new Error(err); + errObj = new Error(err); } + errObj.args = args; + + clearTimeout(timeout); child.removeAllListeners('exit'); child.kill(); // call the callback if there is one @@ -177,11 +210,11 @@ function wkhtmltopdf(input, options, callback) { } child.once('error', function(err) { - throw new Error(err); // critical error + handleError(err); // critical error }); child.stderr.on('data', function(data) { - stderrMessages.push((data || '').toString()); + stderrMessages.push(data); if (options.debug instanceof Function) { options.debug(data); } else if (options.debug) { @@ -205,10 +238,11 @@ function wkhtmltopdf(input, options, callback) { // write input to stdin if it isn't a url if (!isUrl && !isArray) { - // Handle errors on the input stream (happens when command cannot run) - child.stdin.on('error', handleError); if (isStream(input)) { - input.pipe(child.stdin); + input.pipe(child.stdin) + .on('error', function(e) { + stderrMessages.parallelError = e; + }); } else { child.stdin.end(input); }