A "Full" Error Stream Blocks the wkhtmltopdf Process
See original GitHub issueIn method Pdf.getPDF, a “full” error stream blocks the wkhtmltopdf process. By “full” I mean that the wkhtmltopdf stderr’s buffer is full, causing wkhtmltopdf to block until the error stream is consumed, which will never happen. I fixed this by consuming the streams concurrently in separate Executors.
My Pdf.java:
package com.github.jhonnymertz.wkhtmltopdf.wrapper;
import com.github.jhonnymertz.wkhtmltopdf.wrapper.configurations.WrapperConfig;
import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.Page;
import com.github.jhonnymertz.wkhtmltopdf.wrapper.page.PageType;
import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Param;
import com.github.jhonnymertz.wkhtmltopdf.wrapper.params.Params;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Represents a Pdf file
*/
public class Pdf {
private static final String STDINOUT = "-";
private final WrapperConfig wrapperConfig;
private final Params params;
private final List<Page> pages;
private boolean hasToc = false;
public Pdf() {
this(new WrapperConfig());
}
public Pdf(WrapperConfig wrapperConfig) {
this.wrapperConfig = wrapperConfig;
this.params = new Params();
this.pages = new ArrayList<Page>();
}
/**
* Add a page to the pdf
*
* @deprecated Use the specific type method to a better semantic
*/
@Deprecated
public void addPage(String source, PageType type) {
this.pages.add(new Page(source, type));
}
/**
* Add a page from an URL to the pdf
*/
public void addPageFromUrl(String source) {
this.pages.add(new Page(source, PageType.url));
}
/**
* Add a page from a HTML-based string to the pdf
*/
public void addPageFromString(String source) {
this.pages.add(new Page(source, PageType.htmlAsString));
}
/**
* Add a page from a file to the pdf
*/
public void addPageFromFile(String source) {
this.pages.add(new Page(source, PageType.file));
}
public void addToc() {
this.hasToc = true;
}
public void addParam(Param param, Param... params) {
this.params.add(param, params);
}
public File saveAs(String path) throws IOException, InterruptedException {
File file = new File(path);
FileUtils.writeByteArrayToFile(file, getPDF());
return file;
}
public byte[] getPDF() throws IOException, InterruptedException {
Future<byte[]> inputStreamToByteArray = null;
Future<byte[]> errorStreamToByteArray = null;
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Process process = Runtime.getRuntime().exec(getCommandAsArray());
inputStreamToByteArray = executor.submit(streamToByteArrayTask(process.getInputStream()));
errorStreamToByteArray = executor.submit(streamToByteArrayTask(process.getErrorStream()));
process.waitFor();
if (process.exitValue() != 0) {
throw new RuntimeException("Process (" + getCommand() + ") exited with status code " + process.exitValue() + ":\n" + new String(getFuture(errorStreamToByteArray)));
}
return getFuture(inputStreamToByteArray);
} finally {
cancelFutures(inputStreamToByteArray, errorStreamToByteArray);
cleanTempFiles();
}
}
private String[] getCommandAsArray() throws IOException {
List<String> commandLine = new ArrayList<String>();
if (wrapperConfig.isXvfbEnabled())
commandLine.addAll(wrapperConfig.getXvfbConfig().getCommandLine());
commandLine.add(wrapperConfig.getWkhtmltopdfCommand());
commandLine.addAll(params.getParamsAsStringList());
if (hasToc)
commandLine.add("toc");
for (Page page : pages) {
if (page.getType().equals(PageType.htmlAsString)) {
File temp = File.createTempFile("java-wkhtmltopdf-wrapper" + UUID.randomUUID().toString(), ".html");
FileUtils.writeStringToFile(temp, page.getSource(), "UTF-8");
page.setSource(temp.getAbsolutePath());
}
commandLine.add(page.getSource());
}
commandLine.add(STDINOUT);
return commandLine.toArray(new String[commandLine.size()]);
}
private Callable<byte[]> streamToByteArrayTask(final InputStream input) {
return new Callable<byte[]>() {
public byte[] call() throws Exception {
return IOUtils.toByteArray(input);
}
};
}
private byte[] getFuture(Future<byte[]> future) {
try {
return future.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void cancelFutures(Future<?>... futures) {
for (Future future: futures){
if (future != null && !future.isDone()) {
future.cancel(true);
}
}
}
private void cleanTempFiles() {
for (Page page : pages) {
if (page.getType().equals(PageType.htmlAsString)) {
new File(page.getSource()).delete();
}
}
}
public String getCommand() throws IOException {
return StringUtils.join(getCommandAsArray(), " ");
}
}
Issue Analytics
- State:
- Created 6 years ago
- Comments:11 (11 by maintainers)
Top Results From Across the Web
wkhtmltopdf, 0.12.6, Warning: Blocked access to file
This is caused by the change of default behavior in version 0.12.6 of wkhtmltopdf . wkhtmltopdf disables local file access by default now....
Read more >wkhtmltopdf. Error : Failed to load about:blank..
Hello,. When I publish a Work Instruction as a pdf, all the images are missing from the pdf. The CMD window shows the...
Read more >Process.StandardOutput Property (System.Diagnostics)
A deadlock condition results if the parent process calls p.StandardOutput.ReadToEnd followed by p.StandardError.ReadToEnd and the child process writes enough ...
Read more >GitHub Docs Status Support Downloads - wkhtmltopdf
All downloads are currently hosted via GitHub releases, so you can browse for a specific download or use the links below. Do not...
Read more >[Solved] Access to the path is denied. - CodeProject
i just deployed a webapp, and am getting the error below. The webapp needs to write to files in the app_data, and am...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Thank you all!
You can change if you want. I’ll review it before the next release.