Render pipeline
Tracing a POST /templates/:name/render end to end.
-
HTTP —
server.Handlers.RenderData(app/server/handlers.go) unescapes the template name, rejects empty bodies, binds the JSON body intomap[string]any, and callspdfRenderService.RenderPDFWithData. Errors are mapped to status codes bybuildError; success writesapplication/pdf. -
Lookup + validation —
BasePDFRenderService.RenderPDFWithData(app/services/pdf_render_service.go) fetches the template from the store, runs the JSON-schema validator if present (failure →422), then awaitstemplate.Render(ctx, data). -
HTML render — for an
HTML2PDFTemplate(app/templates/fileloader/html_2_pdf_template.go),Renderschedules work on thefutures.Scheduler. The worker renders body/header/footer via the chosen engine, builds apdfrender.Document, and mapsparams.jsonto apdfrender.Config. -
Browser render —
cdp.Render.RenderPDF(app/pdfrender/cdp/render.go) applies the render timeout, stores the document in the internal render store (getting a document ID), computes the internal-server URL, builds theprintToPDFparameters, and borrows a browser from the pool with retries. Inside the borrow,printPDFnavigates the page to the internal URL, waits for the configured lifecycle event (or the callback), and callsPrintToPDF. -
HTML delivery — the browser fetches the stored HTML from the internal callback server; assets resolve against the same server.
-
Return — the base64 PDF from CDP is decoded and bubbles back up through the futures/await chain to the handler, which sets
Content-Lengthand returns the bytes.
For RenderExample, steps 2–3 pull the example data from the template. For
compositions, step 3 fans out: the planner produces a list of tasks, each
task's template is rendered (concurrently), and the resulting PDFs are merged
with pdfcpu. PreviewRenderPlan runs the planner and returns the plan as JSON
without rendering.
Concurrency note
The futures.PoolScheduler worker count equals the browser pool's
MaxTotal. Increasing render throughput means increasing the pool size, not just
the scheduler — the two are wired together in wire_bindings.go.