programing

wkhtmltopdf를 호출하여 HTML에서 PDF 생성

yoursource 2021. 1. 17. 12:24
반응형

wkhtmltopdf를 호출하여 HTML에서 PDF 생성


HTML 파일에서 PDF 파일을 만들려고합니다. 조금 둘러 본 후 나는 wkhtmltopdf 가 완벽 하다는 것을 발견했습니다 . ASP.NET 서버에서이 .exe를 호출해야합니다. 나는 시도했다 :

    Process p = new Process();
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.FileName = HttpContext.Current.Server.MapPath("wkhtmltopdf.exe");
    p.StartInfo.Arguments = "TestPDF.htm TestPDF.pdf";
    p.Start();
    p.WaitForExit();

서버에 파일이 생성되지 않았습니다. 누구든지 올바른 방향으로 나에게 포인터를 줄 수 있습니까? 사이트의 최상위 디렉토리에 wkhtmltopdf.exe 파일을 넣었습니다. 다른 곳에 보관해야합니까?


편집 : 누구든지 html에서 pdf 파일을 동적으로 생성하는 더 나은 솔루션이 있다면 알려주십시오.


업데이트 :
아래 내 대답은 디스크에 pdf 파일을 만듭니다. 그런 다음 해당 파일을 다운로드로 사용자 브라우저에 스트리밍했습니다. 아래 Hath의 답변과 같은 것을 사용하여 wkhtml2pdf를 스트림으로 출력 한 다음 사용자에게 직접 보내면 파일 권한 등과 관련된 많은 문제를 우회 할 수 있습니다.

내 원래 답변 :
서버에서 실행되는 IIS의 ASP.NET 프로세스에서 쓸 수있는 PDF의 출력 경로를 지정했는지 확인하십시오 (일반적으로 NETWORK_SERVICE라고 생각합니다).

내 모습은 다음과 같습니다 (작동합니다).

/// <summary>
/// Convert Html page at a given URL to a PDF file using open-source tool wkhtml2pdf
/// </summary>
/// <param name="Url"></param>
/// <param name="outputFilename"></param>
/// <returns></returns>
public static bool HtmlToPdf(string Url, string outputFilename)
{
    // assemble destination PDF file name
    string filename = ConfigurationManager.AppSettings["ExportFilePath"] + "\\" + outputFilename + ".pdf";

    // get proj no for header
    Project project = new Project(int.Parse(outputFilename));

    var p = new System.Diagnostics.Process();
    p.StartInfo.FileName = ConfigurationManager.AppSettings["HtmlToPdfExePath"];

    string switches = "--print-media-type ";
    switches += "--margin-top 4mm --margin-bottom 4mm --margin-right 0mm --margin-left 0mm ";
    switches += "--page-size A4 ";
    switches += "--no-background ";
    switches += "--redirect-delay 100";

    p.StartInfo.Arguments = switches + " " + Url + " " + filename;

    p.StartInfo.UseShellExecute = false; // needs to be false in order to redirect output
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = true;
    p.StartInfo.RedirectStandardInput = true; // redirect all 3, as it should be all 3 or none
    p.StartInfo.WorkingDirectory = StripFilenameFromFullPath(p.StartInfo.FileName);

    p.Start();

    // read the output here...
    string output = p.StandardOutput.ReadToEnd(); 

    // ...then wait n milliseconds for exit (as after exit, it can't read the output)
    p.WaitForExit(60000); 

    // read the exit code, close process
    int returnCode = p.ExitCode;
    p.Close(); 

    // if 0 or 2, it worked (not sure about other values, I want a better way to confirm this)
    return (returnCode == 0 || returnCode == 2);
}

Windows 서비스와 함께 msmq를 사용하려고 할 때 동일한 문제가 있었지만 어떤 이유로 인해 매우 느 렸습니다. (공정 부분).

이것이 마침내 효과가 있었던 것입니다.

private void DoDownload()
{
    var url = Request.Url.GetLeftPart(UriPartial.Authority) + "/CPCDownload.aspx?IsPDF=False?UserID=" + this.CurrentUser.UserID.ToString();
    var file = WKHtmlToPdf(url);
    if (file != null)
    {
        Response.ContentType = "Application/pdf";
        Response.BinaryWrite(file);
        Response.End();
    }
}

public byte[] WKHtmlToPdf(string url)
{
    var fileName = " - ";
    var wkhtmlDir = "C:\\Program Files\\wkhtmltopdf\\";
    var wkhtml = "C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe";
    var p = new Process();

    p.StartInfo.CreateNoWindow = true;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = true;
    p.StartInfo.RedirectStandardInput = true;
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.FileName = wkhtml;
    p.StartInfo.WorkingDirectory = wkhtmlDir;

    string switches = "";
    switches += "--print-media-type ";
    switches += "--margin-top 10mm --margin-bottom 10mm --margin-right 10mm --margin-left 10mm ";
    switches += "--page-size Letter ";
    p.StartInfo.Arguments = switches + " " + url + " " + fileName;
    p.Start();

    //read output
    byte[] buffer = new byte[32768];
    byte[] file;
    using(var ms = new MemoryStream())
    {
        while(true)
        {
            int read =  p.StandardOutput.BaseStream.Read(buffer, 0,buffer.Length);

            if(read <=0)
            {
                break;
            }
            ms.Write(buffer, 0, read);
        }
        file = ms.ToArray();
    }

    // wait or exit
    p.WaitForExit(60000);

    // read the exit code, close process
    int returnCode = p.ExitCode;
    p.Close();

    return returnCode == 0 ? file : null;
}

Graham Ambrose와 다른 모든 사람들에게 감사드립니다.


좋아요, 이것은 오래된 질문이지만 훌륭한 질문입니다. 그리고 좋은 답을 찾지 못했기 때문에 직접 만들었습니다. :) 또한이 매우 간단한 프로젝트를 GitHub에 게시했습니다.

다음은 몇 가지 샘플 코드입니다.

var pdfData = HtmlToXConverter.ConvertToPdf("<h1>SOO COOL!</h1>");

다음은 몇 가지 핵심 사항입니다.

  • P / Invoke 없음
  • 새로운 프로세스 생성 없음
  • 파일 시스템 없음 (모두 RAM)
  • intellisense 등이 포함 된 네이티브 .NET DLL
  • PDF 또는 PNG 생성 기능 ( HtmlToXConverter.ConvertToPng)

wkhtmltopdf 라이브러리에 대한 C # 래퍼 라이브러리 (P / Invoke 사용)를 확인하십시오. https://github.com/pruiz/WkHtmlToXSharp


이것이 일반적으로 나쁜 생각 인 데는 여러 가지 이유가 있습니다. 스폰되지만 충돌이 발생하면 메모리에 남아있는 실행 파일을 어떻게 제어 할 것입니까? 서비스 거부 공격은 어떻습니까? 또는 악의적 인 것이 TestPDF.htm에 침입하는 경우에는 어떻습니까?

내 이해는 ASP.NET 사용자 계정에는 로컬 로그온 권한이 없다는 것입니다. 또한 실행 파일에 액세스하고 파일 시스템에 쓰기 위해 올바른 파일 권한이 있어야합니다. 로컬 보안 정책을 편집하고 ASP.NET 사용자 계정 (아마도 ASPNET)이 로컬로 로그온하도록해야합니다 (기본적으로 거부 목록에있을 수 있음). 그런 다음 다른 파일에 대한 NTFS 파일 시스템의 권한을 편집해야합니다. 공유 호스팅 환경에있는 경우 필요한 구성을 적용하지 못할 수 있습니다.

The best way to use an external executable like this is to queue jobs from the ASP.NET code and have some sort of service monitor the queue. If you do this you will protect yourself from all sorts of bad things happening. The maintenance issues with changing the user account are not worth the effort in my opinion, and whilst setting up a service or scheduled job is a pain, its just a better design. The ASP.NET page should poll a result queue for the output and you can present the user with a wait page. This is acceptable in most cases.


You can tell wkhtmltopdf to send it's output to sout by specifying "-" as the output file. You can then read the output from the process into the response stream and avoid the permissions issues with writing to the file system.


Thanks for the question / answer / all the comments above. I came upon this when I was writing my own C# wrapper for WKHTMLtoPDF and it answered a couple of the problems I had. I ended up writing about this in a blog post - which also contains my wrapper (you'll no doubt see the "inspiration" from the entries above seeping into my code...)

http://icanmakethiswork.blogspot.de/2012/04/making-pdfs-from-html-in-c-using.html

Thanks again guys!


My take on this with 2018 stuff.

I am using async. I am streaming to and from wkhtmltopdf. I created a new StreamWriter because wkhtmltopdf is expecting utf-8 by default but it is set to something else when the process starts.

I didn't include a lot of arguments since those varies from user to user. You can add what you need using additionalArgs.

I removed p.WaitForExit(...) since I wasn't handling if it fails and it would hang anyway on await tStandardOutput. If timeout is needed, then you would have to call Wait(...) on the different tasks with a cancellationtoken or timeout and handle accordingly.

public async Task<byte[]> GeneratePdf(string html, string additionalArgs)
{
    ProcessStartInfo psi = new ProcessStartInfo
    {
        FileName = @"C:\Program Files\wkhtmltopdf\wkhtmltopdf.exe",
        UseShellExecute = false,
        CreateNoWindow = true,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        Arguments = "-q -n " + additionalArgs + " - -";
    };

    using (var p = Process.Start(psi))
    using (var pdfSream = new MemoryStream())
    using (var utf8Writer = new StreamWriter(p.StandardInput.BaseStream, 
                                             Encoding.UTF8))
    {
        await utf8Writer.WriteAsync(html);
        utf8Writer.Close();
        var tStdOut = p.StandardOutput.BaseStream.CopyToAsync(pdfSream);
        var tStdError = p.StandardError.ReadToEndAsync();

        await tStandardOutput;
        string errors = await tStandardError;

        if (!string.IsNullOrEmpty(errors)) { /* deal/log with errors */ }

        return pdfSream.ToArray();
    }
}

Things I haven't included in there but could be useful if you have images, css or other stuff that wkhtmltopdf will have to load when rendering the html page:

  • you can pass the authentication cookie using --cookie
  • in the header of the html page, you can set the base tag with href pointing to the server and wkhtmltopdf will use that if need be

The ASP .Net process probably doesn't have write access to the directory.

Try telling it to write to %TEMP%, and see if it works.

Also, make your ASP .Net page echo the process's stdout and stderr, and check for error messages.


Generally return code =0 is coming if the pdf file is created properly and correctly.If it's not created then the value is in -ve range.


using System;
using System.Diagnostics;
using System.Web;

public partial class pdftest : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    private void fn_test()
    {
        try
        {
            string url = HttpContext.Current.Request.Url.AbsoluteUri;
            Response.Write(url);
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = 
                @"C:\PROGRA~1\WKHTML~1\wkhtmltopdf.exe";//"wkhtmltopdf.exe";
            startInfo.Arguments = url + @" C:\test"
                 + Guid.NewGuid().ToString() + ".pdf";
            Process.Start(startInfo);
        }
        catch (Exception ex)
        {
            string xx = ex.Message.ToString();
            Response.Write("<br>" + xx);
        }
    }
    protected void btn_test_Click(object sender, EventArgs e)
    {
        fn_test();
    }
}

ReferenceURL : https://stackoverflow.com/questions/1331926/calling-wkhtmltopdf-to-generate-pdf-from-html

반응형