wkhtmltopdf 与 IronPDF:技术比较指南
当.NET 开发人员需要将 HTML 转换为 PDF 时,wkhtmltopdf 因其开源性和命令行简易性历来是最受欢迎的选择。 然而,项目的废弃和严重的安全漏洞促使许多团队开始评估现代的替代方案。 本技术比较将对wkhtmltopdf和IronPDF进行研究,以帮助架构师和开发人员了解它们在安全态势、渲染能力和长期可行性方面的显著差异。
了解 wkhtmltopdf.
wkhtmltopdf 是一款将 HTML 转换为 PDF 文档的工具,可直接从命令行运行,并利用 Qt WebKit 处理 HTML 内容。 在多年的积极开发过程中,该库因其免费的 LGPLv3 许可和跨平台可用性而广受欢迎。
然而,wkhtmltopdf 目前面临着不容忽视的严峻挑战:
项目终止:最后一次有意义的软件更新发生在 2016-2017 年左右。 -严重安全漏洞: CVE-2022-35583(CVSS 9.8 严重级别)是一个尚未修复的 SSRF 漏洞。 -渲染引擎过时:依赖于 2015 年的 Qt WebKit -现代 Web 支持有限:不支持 CSS Grid,Flexbox 实现存在问题,不支持 ES6+ JavaScript -生态系统停滞:所有 .NET 封装库(DinkToPdf、Rotativa、TuesPechkin、WkHtmlToPdf-DotNet、NReco.PdfGenerator)都继承了这些漏洞
CVE-2022-35583 安全危机
wkhtmltopdf 中的服务器端请求伪造 (SSRF) 漏洞允许攻击者
-访问内部服务:访问防火墙后的内部 API、数据库和服务 -窃取凭证:访问云元数据端点(AWS、GCP、Azure)以窃取 IAM 凭证 -端口扫描:从基础设施内部扫描内部网络 数据泄露:通过精心设计的 HTML/CSS 代码提取敏感数据
攻击载体简单明了--向 PDF 生成器提交恶意 HTML:
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin"/>
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin"/>当wkhtmltopdf渲染 HTML 时,它会绕过防火墙和安全控制,从服务器的网络上下文中获取这些 URL。 该漏洞永远不会被修补,因为该项目已被正式放弃。
了解IronPDF
IronPDF 提供了一个强大的替代方案,解决了wkhtmltopdf的不足之处。 IronPDF拥有积极的维护、定期的更新,并依赖于当前的 Chromium 渲染引擎,因此既安全又符合现代网络标准。
主要特点包括
-现代 Chromium 引擎:使用最新的 Chromium 渲染引擎,并完全支持 ES2024 JavaScript -无已知 CVE:零已知安全漏洞 -积极开发:定期发布安全更新和功能增强版本 -完全支持 CSS:全面支持 CSS Grid、Flexbox 和现代布局系统 -全面的PDF功能:数字签名、PDF/A合规性、PDF操作功能 -专业支持:详尽的文档和专属支持渠道
功能对比
下表强调了wkhtmltopdf和IronPDF之间的基本差异:
| 特征 | wkhtmltopdf | IronPDF |
|---|---|---|
| 许可 | LGPLv3 (免费) | 商业翻译 |
| 渲染引擎 | Qt WebKit (2015) | 当前的 Chromium 引擎 |
| 安全状态 | CVE-2022-35583 关键 (9.8) 未打补丁 | 无已知 CVE |
| 最近一次有意义的更新 | 2016-2017 | 积极开发 |
| CSS 网格 | 不支持 | 支持 |
| Flexbox | 破译 | 支持 |
| ES6+JavaScript> | 不支持 | 支持 |
| 同步/等待 | 不支持 | 支持 |
| PDF 操作 | 不支持 | 支持 |
| 数字签名 | 不支持 | 支持 |
| PDF/A合规性 | 不支持 | 支持 |
| 专业支持 | 无(已放弃) | 有服务水平协议的商业翻译 |
| C#集成 | 通过第三方封装 | 直接、定期更新 |
受影响的封装库
wkhtmltopdf 的所有 .NET 封装程序都继承了相同的漏洞:
| 封装库 | 现状 | 安全风险 |
|---|---|---|
| DinkToPdf | 放弃 | 关键 |
| Rotativa | 放弃 | 关键 |
| TuesPechkin | 放弃 | 关键 |
| WkHtmlToPdf-DotNet | 放弃 | 关键 |
| NReco.PdfGenerator | 使用 wkhtmltopdf | 关键 |
如果您的应用程序使用了这些库中的任何一个,那么它就存在 CVE-2022-35583 漏洞。
API 架构差异
wkhtmltopdf wrappers 和IronPDF之间的 API 模式显示了在复杂性和可用性方面的显著差异。
wkhtmltopdf配置模式
wkhtmltopdf 封装程序要求创建具有嵌套设置配置的文档对象:
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4
},
Objects = {
new ObjectSettings()
{
HtmlContent = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("output.pdf", pdf);
}
}// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4
},
Objects = {
new ObjectSettings()
{
HtmlContent = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("output.pdf", pdf);
}
}' NuGet: Install-Package WkHtmlToPdf-DotNet
Imports WkHtmlToPdfDotNet
Imports WkHtmlToPdfDotNet.Contracts
Imports System.IO
Class Program
Shared Sub Main()
Dim converter = New SynchronizedConverter(New PdfTools())
Dim doc = New HtmlToPdfDocument() With {
.GlobalSettings = New GlobalSettings() With {
.ColorMode = ColorMode.Color,
.Orientation = Orientation.Portrait,
.PaperSize = PaperKind.A4
},
.Objects = {
New ObjectSettings() With {
.HtmlContent = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
}
}
}
Dim pdf As Byte() = converter.Convert(doc)
File.WriteAllBytes("output.pdf", pdf)
End Sub
End Class这种模式要求使用 PdfTools 创建同步转换器,使用 GlobalSettings 和 Objects 集合构建HtmlToPdfDocument并手动将字节数组写入文件。
IronPDF简化模式
IronPDF 使用ChromePdfRenderer类来简化方法:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><p>This is a PDF from HTML.</p>");
pdf.SaveAs("output.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><p>This is a PDF from HTML.</p>");
pdf.SaveAs("output.pdf");
}
}Imports IronPdf
Imports System
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><p>This is a PDF from HTML.</p>")
pdf.SaveAs("output.pdf")
End Sub
End ClassChromePdfRenderer类消除了嵌套的配置对象,返回一个具有内置保存方法的PdfDocument。 有关全面的 HTML 转换指导,请参阅 HTML 转 PDF 教程。
将 URL 转换为 PDF.
将网页转换为 PDF 演示了两种方法之间的复杂性差异。
wkhtmltopdf的实现
wkhtmltopdf 使用 ObjectSettings 中的 Page 属性指定 URL:
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4
},
Objects = {
new ObjectSettings()
{
Page = "https://www.example.com"
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("webpage.pdf", pdf);
}
}// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4
},
Objects = {
new ObjectSettings()
{
Page = "https://www.example.com"
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("webpage.pdf", pdf);
}
}' NuGet: Install-Package WkHtmlToPdf-DotNet
Imports WkHtmlToPdfDotNet
Imports WkHtmlToPdfDotNet.Contracts
Imports System.IO
Module Program
Sub Main()
Dim converter As New SynchronizedConverter(New PdfTools())
Dim doc As New HtmlToPdfDocument() With {
.GlobalSettings = New GlobalSettings() With {
.ColorMode = ColorMode.Color,
.Orientation = Orientation.Portrait,
.PaperSize = PaperKind.A4
},
.Objects = {
New ObjectSettings() With {
.Page = "https://www.example.com"
}
}
}
Dim pdf As Byte() = converter.Convert(doc)
File.WriteAllBytes("webpage.pdf", pdf)
End Sub
End ModuleIronPdf 的实现
IronPDF 提供了专门的 RenderUrlAsPdf 方法:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
pdf.SaveAs("webpage.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
pdf.SaveAs("webpage.pdf");
}
}Imports IronPdf
Imports System
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim pdf = renderer.RenderUrlAsPdf("https://www.example.com")
pdf.SaveAs("webpage.pdf")
End Sub
End ClassRenderUrlAsPdf 方法利用 Chromium 引擎来呈现具有完整 JavaScript 执行功能和现代 CSS 支持功能的页面--这些功能受到wkhtmltopdf的 2015 WebKit 引擎的限制。
自定义 PDF 设置
配置页面尺寸、页边距和方向可以显示 API 之间的结构差异。
wkhtmltopdf自定义设置
wkhtmltopdf 需要嵌套 GlobalSettings 与 MarginSettings 对象:
// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4,
Margins = new MarginSettings() { Top = 10, Bottom = 10, Left = 10, Right = 10 }
},
Objects = {
new ObjectSettings()
{
Page = "input.html",
WebSettings = { DefaultEncoding = "utf-8" }
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("custom-output.pdf", pdf);
}
}// NuGet: Install-Package WkHtmlToPdf-DotNet
using WkHtmlToPdfDotNet;
using WkHtmlToPdfDotNet.Contracts;
using System.IO;
class Program
{
static void Main()
{
var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument()
{
GlobalSettings = {
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4,
Margins = new MarginSettings() { Top = 10, Bottom = 10, Left = 10, Right = 10 }
},
Objects = {
new ObjectSettings()
{
Page = "input.html",
WebSettings = { DefaultEncoding = "utf-8" }
}
}
};
byte[] pdf = converter.Convert(doc);
File.WriteAllBytes("custom-output.pdf", pdf);
}
}' NuGet: Install-Package WkHtmlToPdf-DotNet
Imports WkHtmlToPdfDotNet
Imports WkHtmlToPdfDotNet.Contracts
Imports System.IO
Class Program
Shared Sub Main()
Dim converter As New SynchronizedConverter(New PdfTools())
Dim doc As New HtmlToPdfDocument() With {
.GlobalSettings = New GlobalSettings() With {
.ColorMode = ColorMode.Color,
.Orientation = Orientation.Landscape,
.PaperSize = PaperKind.A4,
.Margins = New MarginSettings() With {.Top = 10, .Bottom = 10, .Left = 10, .Right = 10}
},
.Objects = {
New ObjectSettings() With {
.Page = "input.html",
.WebSettings = New WebSettings() With {.DefaultEncoding = "utf-8"}
}
}
}
Dim pdf As Byte() = converter.Convert(doc)
File.WriteAllBytes("custom-output.pdf", pdf)
End Sub
End ClassIronPDF自定义设置
IronPDF 使用渲染选项属性进行直接配置:
// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape;
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
renderer.RenderingOptions.MarginLeft = 10;
renderer.RenderingOptions.MarginRight = 10;
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
var pdf = renderer.RenderHtmlFileAsPdf("input.html");
pdf.SaveAs("custom-output.pdf");
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape;
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
renderer.RenderingOptions.MarginLeft = 10;
renderer.RenderingOptions.MarginRight = 10;
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
var pdf = renderer.RenderHtmlFileAsPdf("input.html");
pdf.SaveAs("custom-output.pdf");
}
}Imports IronPdf
Imports IronPdf.Rendering
Imports System
Class Program
Shared Sub Main()
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape
renderer.RenderingOptions.MarginTop = 10
renderer.RenderingOptions.MarginBottom = 10
renderer.RenderingOptions.MarginLeft = 10
renderer.RenderingOptions.MarginRight = 10
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4
Dim pdf = renderer.RenderHtmlFileAsPdf("input.html")
pdf.SaveAs("custom-output.pdf")
End Sub
End ClassAPI 映射参考
正在评估从wkhtmltopdf过渡到IronPDF的团队会发现此映射有助于理解概念等同:
CLI 到 C# API 映射
| wkhtmltopdf CLI 选项 | IronPdf 同等产品 |
|---|---|
wkhtmltopdf input.html output.pdf | renderer.RenderHtmlFileAsPdf() |
wkhtmltopdf URL output.pdf | renderer.RenderUrlAsPdf() |
--页面大小 A4 | RenderingOptions.PaperSize = PdfPaperSize.A4 |
--页面大小 Letter</code>|<code>RenderingOptions.PaperSize = PdfPaperSize.Letter | |
--面向景观 | RenderingOptions.PaperOrientation = Landscape |
--margin-top 10mm``|RenderingOptions.MarginTop = 10` | |
--margin-bottom 10mm | RenderingOptions.MarginBottom = 10 |
--边距左 10 毫米 | RenderingOptions.MarginLeft = 10 |
--右边距 10 毫米 | RenderingOptions.MarginRight = 10 |
--header-html header.html | RenderingOptions.HtmlHeader |
--footer-html footer.html | RenderingOptions.HtmlFooter |
--footer-center "[page]" | {page} 占位符 |
--footer-center"[toPage]" | {total-pages} 占位符 |
--enable-javascript | 默认已启用 |
--javascript--延迟 500 | RenderingOptions.WaitFor.RenderDelay = 500 |
--打印媒体类型 | RenderingOptions.CssMediaType = 打印 |
--dpi 300 | RenderingOptions.Dpi=300 |
--grayscale | RenderingOptions.GrayScale = true<br |
--zoom 0.8 | RenderingOptions.Zoom = 80 |
C# Wrapper API 映射
| wkhtmltopdf 封装程序 | IronPDF |
|---|---|
同步转换器 | ChromePdfRenderer |
HtmlToPdfDocument | 渲染选项 |
GlobalSettings.Out | pdf.SaveAs() |
GlobalSettings.PaperSize | RenderingOptions.PaperSize |
GlobalSettings.Orientation | RenderingOptions.PaperOrientation |
GlobalSettings.Margins | RenderingOptions.Margin* |
对象设置.页面 | RenderHtmlFileAsPdf() |
ObjectSettings.HtmlContent | RenderHtmlAsPdf() |
HeaderSettings.Center | TextHeader.CenterText |
FooterSettings.Center | TextFooter.CenterText |
converter.Convert(doc) | renderer.RenderHtmlAsPdf() |
占位符语法映射
| wkhtmltopdf 占位符 | IronPdf 占位符 |
|---|---|
[页面] | {page} |
[toPage] | {总页数} |
[日期] | {日期} |
[时间] | {时间} |
[标题] | {html-title} |
[url] | {url} |
团队何时考虑从wkhtmltopdf迁移到 IronPDF?
有几种情况通常会促使开发团队将IronPDF作为wkhtmltopdf的替代品进行评估:
安全合规要求
有安全合规要求(SOC 2、PCI DSS、HIPAA)的组织不能接受存在已知关键漏洞的应用程序。 CVE-2022-35583 的严重性等级为 9.8,在大多数安全框架中都会触发立即修复的要求。
现代 CSS 框架的采用
采用 Bootstrap 5、Tailwind CSS 或自定义 CSS 网格布局的团队会发现wkhtmltopdf无法正确呈现这些布局。 2015 WebKit 引擎完全不支持 CSS 网格,并且破坏了 Flexbox 的实现。
JavaScript 应用程序要求
使用现代 JavaScript 功能(ES6+ 语法,包括箭头函数、async/await、类和模板字面)的应用程序会在wkhtmltopdf中遇到故障。IronPDF的 Chromium 引擎提供完整的 JavaScript 支持。
云和容器部署
使用 Docker、Kubernetes 或云平台的现代部署策略受益于 IronPdf 的容器友好架构。 容器中wkhtmltopdf二进制文件的安全扫描将标记 CVE 漏洞。
长期维护问题
由于wkhtmltopdf今后预计不会更新,因此随着网络标准的发展,团队将面临越来越多的技术债务。IronPDF的积极开发确保了与未来 .NET 版本的持续兼容性,包括预计在 2026 年推出的 .NET 10。
IronPDF的其他功能
除了 HTML 到 PDF 的转换,IronPDF 还提供wkhtmltopdf无法提供的文档操作功能:
-合并 PDF :将多个文档合并成单个文件 -拆分文档:将页面范围提取到单独的 PDF 文件中 -数字签名:应用加密签名来验证文档的真实性 -添加水印:添加文字或图片水印
- PDF/A 合规性:生成符合存档标准的文档 -表单填充:通过编程方式填充 PDF 表单字段 -密码保护:使用用户密码和所有者密码加密 PDF 文件 -页眉和页脚:自动页码和品牌标识,并完全支持 HTML/CSS
同步支持
IronPdf 为网络应用程序性能提供异步/等待支持:
public async Task<byte[]> GeneratePdfAsync(string html)
{
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return pdf.BinaryData;
}public async Task<byte[]> GeneratePdfAsync(string html)
{
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return pdf.BinaryData;
}Imports System.Threading.Tasks
Public Async Function GeneratePdfAsync(html As String) As Task(Of Byte())
Dim renderer As New ChromePdfRenderer()
Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html)
Return pdf.BinaryData
End Functionwkhtmltopdf 的同步封装器可以防止高负载网络应用程序中的线程阻塞--这是wkhtmltopdf仅同步封装器不具备的功能。
.NET兼容性和未来准备情况
弃用wkhtmltopdf意味着无法进行兼容性测试或更新较新的 .NET 版本。IronPDFfor .NET 保持着定期更新的积极开发态势,确保与 .NET 8、.NET 9 和未来版本(包括预计于 2026 年发布的 .NET 10)的兼容性。该库的整个 API 均支持 async/await,符合现代 C# 开发实践,包括 C# 14 中的预期功能。
结论
wkhtmltopdf 和IronPDF在安全性、渲染能力和长期可行性方面存在显著差异。wkhtmltopdf的关键 SSRF 漏洞 (CVE-2022-35583) 与项目放弃相结合,为生产应用程序带来了难以承受的安全态势。 2015 WebKit 引擎无法处理现代 CSS 网格,对 Flexbox 的支持已被破坏,并且无法支持 ES6+ JavaScript。
IronPDF 基于 Chromium 的渲染引擎完全支持现代网络标准,同时保持零已知 CVE。 其简化的 API 设计--方法如RenderHtmlAsPdf()和 SaveAs() 而不是嵌套的配置对象--降低了代码的复杂性,同时增加了wkhtmltopdf无法提供的功能,如 PDF 操作、数字签名和异步支持。
对于目前正在使用wkhtmltopdf或其封装库(DinkToPdf、Rotativa、TuesPechkin)的团队来说,安全问题需要立即评估替代方案。wkhtmltopdfCLI 选项与IronPDF的 RenderingOptions 之间的 API 映射简单明了,IronPDF 始终需要较少的代码,同时消除了wkhtmltopdf固有的安全风险。
有关更多实施指导,请浏览 IronPDF 文档和涵盖特定用例和高级功能的 教程。