You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8+wkhtmltopdf部署Docker时出现Pipe Broken错误求解决

问题:Docker环境下wkhtmltopdf转换PDF抛出“Pipe is broken”异常

本地Windows环境使用wkhtmltopdf将cshtml文件转换为PDF完全正常,但部署到Docker容器后,调用generatePdf.GetPDF(html)时抛出Pipe is broken异常。


相关环境与代码

1. Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base

RUN apt-get update && apt-get install -y --no-install-recommends wget ca-certificates fontconfig libc6 libfreetype6 libjpeg62-turbo libpng16-16 libssl3 libstdc++6 libx11-6 libxcb1 libxext6 libxrender1 xfonts-75dpi xfonts-base zlib1g
RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb
RUN dpkg -i wkhtmltox_0.12.6.1-3.bookworm_amd64.deb

WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["nuget.config", "."]
COPY ["src/Application/Application.csproj", "src/Application/"]
COPY ["src/Application/Rotativa", "src/Application/Rotativa"]
COPY ["src/Services/Services.csproj", "src/Services/"]
RUN dotnet restore "./src/Application/Application.csproj"
COPY . .
WORKDIR "/src/src/Application"
RUN dotnet build "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Application.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
RUN chmod -R 755 /app/Rotativa/
ENTRYPOINT ["dotnet", "Application.dll"]

2. PDF生成类核心代码

using RazorEngineCore;
using Services.Features.TransferRequest.Models.Interfaces;
using Services.Features.TransferRequest.Repositories;
using Shared.Infrastructure.Interfaces;
using Shared.Infrastructure.ManipulateFiles;
using System.Collections.Concurrent;
using System.Xml.Linq;
using Wkhtmltopdf.NetCore;
using Wkhtmltopdf.NetCore.Options;

namespace Services.Features.TransferRequest.UseCases.Services;

public class PdfGeneratorService(IRazorEngine razorEngine, IGeneratePdf generatePdf, ILoggerActionEnrichService logger, IFilesReader filesReader) : IPdfGeneratorService
{
    private readonly string _namespace = $"{typeof(TransferRequestModule).Namespace}.Models.Templates";
    public async Task<MemoryStream> GeneratePdfAsync<TModel>(TModel model, string fileName)
    {
        try
        {
            var template = await filesReader.ReadFileWithMemoryFirstAsync(_namespace, fileName, TypeArchives.CSHTML);
            var html = await GenerateHtmlAsync<TModel>(model, template);

            return await GeneratePdf(html);
        }
        catch (Exception ex)
        {
            logger.Add($"Problemas para gerar o pdf \n Message: {ex.Message}");
            logger.Add($"StackTrace: {ex.StackTrace}");
            logger.Add($"InnerException: {ex.InnerException}");
            logger.Add($"Exception: {ex}");
            throw;
        }
    }

    private async Task<MemoryStream> GeneratePdf(string html)
    {
        generatePdf.SetConvertOptions(new ConvertOptions
        {
            PageMargins = new Margins(0, 0, 0, 0),
        });

        var pdf = await Task.Run(() => generatePdf.GetPDF(html));
        var pdfStream = new MemoryStream();
        await pdfStream.WriteAsync(pdf, 0, pdf.Length);
        pdfStream.Position = 0;
        return pdfStream;
    }

    private async Task<string> GenerateHtmlAsync<TModel>(TModel model, string template)
        => await (await razorEngine.CompileAsync<RazorEngineTemplateBase<TModel>>(template))
            .RunAsync(instance =>
            {
                instance.Model = model;
            });
}

3. 报错位置及堆栈信息

报错行:

var pdf = await Task.Run(() => generatePdf.GetPDF(html));

堆栈信息:

StackTrace:    at System.IO.Pipes.PipeStream.CheckWriteOperations()
   at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
   at System.IO.StreamWriter.Dispose(Boolean disposing)
   at System.IO.TextWriter.Dispose()
   at Wkhtmltopdf.NetCore.WkhtmlDriver.Convert(String wkhtmlPath, String switches, String html)
   at Wkhtmltopdf.NetCore.GeneratePdf.GetPDF(String html)
   at Services.Features.TransferRequest.UseCases.Services.PdfGeneratorService.<>c__DisplayClass7_0.<GeneratePdf>b__0() in /src/src/Services/Features/TransferRequest/UseCases/Services/PdfGeneratorService.cs:line 42
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Services.Features.TransferRequest.UseCases.Services.PdfGeneratorService.GeneratePdf(String html) in /src/src/Services/Features/TransferRequest/UseCases/Services/PdfGeneratorService.cs:line 42
   at Services.Features.TransferRequest.UseCases.Services.PdfGeneratorService.GeneratePdfAsync[TModel](TModel model, String fileName) in /src/src/Services/Features/TransferRequest/UseCases/Services/PdfGeneratorService.cs:line 23

解决方案

方案1:确保wkhtmltopdf路径与权限正确

  • 显式指定wkhtmltopdf路径(容器内默认安装路径为/usr/local/bin/wkhtmltopdf):
    private async Task<MemoryStream> GeneratePdf(string html)
    {
        generatePdf.SetWkhtmlPath("/usr/local/bin/wkhtmltopdf"); // 添加此行
        generatePdf.SetConvertOptions(new ConvertOptions
        {
            PageMargins = new Margins(0, 0, 0, 0),
        });
        // ... 其余代码
    }
    
  • 在Dockerfile的final阶段添加权限配置:
    RUN chmod +x /usr/local/bin/wkhtmltopdf
    

方案2:禁用沙箱模式并移除多余线程包装

Linux环境下wkhtmltopdf的沙箱模式易引发管道异常,同时Task.Run属于多余包装,会导致线程上下文切换问题:

private async Task<MemoryStream> GeneratePdf(string html)
{
    generatePdf.SetConvertOptions(new ConvertOptions
    {
        PageMargins = new Margins(0, 0, 0, 0),
        CustomSwitches = "--no-sandbox" // 禁用沙箱
    });

    var pdf = generatePdf.GetPDF(html); // 直接调用,无需Task.Run
    var pdfStream = new MemoryStream();
    await pdfStream.WriteAsync(pdf, 0, pdf.Length);
    pdfStream.Position = 0;
    return pdfStream;
}

方案3:检查HTML编码一致性

确保生成的HTML为UTF-8编码,避免特殊字符导致管道写入失败:

private async Task<MemoryStream> GeneratePdf(string html)
{
    // 统一编码为UTF-8
    var utf8Html = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(html));
    
    generatePdf.SetConvertOptions(new ConvertOptions
    {
        PageMargins = new Margins(0, 0, 0, 0),
        CustomSwitches = "--no-sandbox"
    });

    var pdf = generatePdf.GetPDF(utf8Html);
    var pdfStream = new MemoryStream();
    await pdfStream.WriteAsync(pdf, 0, pdf.Length);
    pdfStream.Position = 0;
    return pdfStream;
}

内容的提问来源于stack exchange,提问作者Paulo Enrique

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 10:09:55