question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

A "Full" Error Stream Blocks the wkhtmltopdf Process

See original GitHub issue

In 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:closed
  • Created 6 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
jhonnymertzcommented, Oct 9, 2017

Thank you all!

0reactions
jhonnymertzcommented, Oct 9, 2017

You can change if you want. I’ll review it before the next release.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found