Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error handling #133

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(('<h1>Test</h1><p>Hello express</p>', { 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
Expand All @@ -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.
Expand Down
54 changes: 44 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 () {
yurks marked this conversation as resolved.
Show resolved Hide resolved
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
}
});
Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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);
}
Expand Down