From 31b08fbabfa4524c1bd2b9eb334e0dd7d2510f76 Mon Sep 17 00:00:00 2001 From: Yurk Sha Date: Sat, 27 Feb 2021 13:16:45 +0200 Subject: [PATCH 1/4] improve error handling --- index.js | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 485d434..3bfd419 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; } @@ -111,14 +111,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 } }); @@ -127,12 +137,19 @@ 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; + // fix cutted lines in Windows + err = Buffer.concat(err).toString(); + var lines = err.split(/[\r\n]+/).map( line => line.trim() ).filter( line => !!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; } @@ -145,10 +162,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 = '_EXIT_FAILURE'; + 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 @@ -164,11 +190,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) { @@ -192,10 +218,11 @@ function wkhtmltopdf(input, options, callback) { // write input to stdin if it isn't a url if (!isUrl) { - // 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); } From b4569acd4bdbd13d93e23cc7e88e92e7731dafce Mon Sep 17 00:00:00 2001 From: Yurk Sha Date: Sat, 6 Mar 2021 17:04:10 +0200 Subject: [PATCH 2/4] Update README.md --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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(('

Test

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. From ce763d34b6cb7518aa85c8c4fb03e61a92377a91 Mon Sep 17 00:00:00 2001 From: Yurk Sha Date: Mon, 25 Jul 2022 12:31:08 +0300 Subject: [PATCH 3/4] fix code styles --- index.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 3bfd419..3efbd04 100644 --- a/index.js +++ b/index.js @@ -142,9 +142,16 @@ function wkhtmltopdf(input, options, callback) { if (Array.isArray(err)) { code = err.code; parallelError = err.parallelError; - // fix cutted lines in Windows + // 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( line => line.trim() ).filter( line => !!line ) + 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; @@ -163,7 +170,7 @@ function wkhtmltopdf(input, options, callback) { } } errObj = new Error(lines[0] || ('Child process finished with exit code ' + code)); - errObj.code = '_EXIT_FAILURE'; + errObj.code = 'WKHTMLTOPDF_EXIT_ERROR'; errObj.errno = code; errObj.details = err; errObj.parallelError = parallelError; @@ -220,9 +227,9 @@ function wkhtmltopdf(input, options, callback) { if (!isUrl) { if (isStream(input)) { input.pipe(child.stdin) - .on('error', function(e) { - stderrMessages.parallelError = e; - }); + .on('error', function(e) { + stderrMessages.parallelError = e; + }); } else { child.stdin.end(input); } From 958d6de652425341dcced86a6ad7c3d8b0707b74 Mon Sep 17 00:00:00 2001 From: Yurk Sha Date: Mon, 25 Jul 2022 13:04:11 +0300 Subject: [PATCH 4/4] fix resolve conflicts issues --- index.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4d75e3d..5e16b58 100644 --- a/index.js +++ b/index.js @@ -87,12 +87,25 @@ function wkhtmltopdf(input, options, callback) { } }); - var isUrl = /^(https?|file):\/\//.test(input); - - if (input) { - args.push(isUrl ? quote(input) : '-'); // stdin if HTML given directly + // Input + var isArray = Array.isArray(input); + if (isArray) { + input.forEach(function(element) { + var isUrl = /^(https?|file):\/\//.test(element); + if (isUrl) { + args.push(quote(element)); + } else { + console.log('[node-wkhtmltopdf] [warn] Multi PDF only supported for URL files (http[s]:// or file://)') + } + }) + } else { + var isUrl = /^(https?|file):\/\//.test(input); + if (input) { + args.push(isUrl ? quote(input) : '-'); // stdin if HTML given directly + } } + // Output args.push(output ? quote(output) : '-'); // stdout if no output file // show the command that is being run if debug opion is passed