Responses
polipagegin.PDF, PDFStream, Preview and DocumentRedirect write Gin responses with the headers Poli Page documents need: Content-Type, Cache-Control: no-store, private, X-Content-Type-Options: nosniff, and an RFC 5987 Content-Disposition. Handlers stay focused on the render call.
polipagegin.PDF(c, bytes, opts) sends a buffered PDF, what you have after client.Render.PDF(...). It sets the status to 200, whatever c.Status said before.
| PDFOptions field | Default | Description |
| --- | --- | --- |
| Filename | document.pdf | The filename shown to the browser. |
| Inline | false | true sends Content-Disposition: inline, to view the PDF in the browser, for example in an iframe. false sends attachment, which downloads it. |
The response carries:
200 OKContent-Type: application/pdfContent-Length: <len(bytes)>Content-Disposition: attachment; filename="invoice-INV-001.pdf"; filename*=UTF-8''invoice-INV-001.pdfCache-Control: no-store, privateX-Content-Type-Options: nosniffFilename encoding
Section titled “Filename encoding”Filename goes into two slots of Content-Disposition:
filename="…"is an ASCII fallback for clients that do not speak RFC 5987. Non-ASCII characters become_, and backslashes and double quotes are backslash-escaped as RFC 6266 section 4.1 asks.filename*=UTF-8''…is the real UTF-8 name browsers display, percent-encoded byte by byte with the RFC 5987attr-charset.ébecomes%C3%A9.
Control characters, including CR and LF, are stripped from the name first, so a filename cannot split the header.
Filename: "facture-élève.pdf"Content-Disposition: attachment; filename="facture-_l_ve.pdf"; filename*=UTF-8''facture-%C3%A9l%C3%A8ve.pdfStreamed PDF
Section titled “Streamed PDF”polipagegin.PDFStream(c, reader, opts) writes the PDF as chunks arrive from Render.PDFStream, without buffering the document. It takes the same PDFOptions, sets X-Accel-Buffering: no, and sends no Content-Length. The handler closes the reader. The Streaming page has the details and the failure modes.
HTML preview
Section titled “HTML preview”polipagegin.Preview(c, html) writes a stored document’s HTML, from Documents.Preview(...).HTML. It sets Content-Type: text/html; charset=utf-8, Cache-Control: no-store, private and X-Content-Type-Options: nosniff, and no Content-Disposition: the response is meant to be rendered.
Redirect to a stored document
Section titled “Redirect to a stored document”polipagegin.DocumentRedirect(c, descriptor) answers 302 Found with Location set to the descriptor’s PresignedPDFURL and Cache-Control: no-store, private. It writes no body: the browser fetches the PDF straight from object storage. A presigned URL lives about 15 minutes, so fetch the descriptor with Documents.Get when you need a fresh one.
Gotchas
Section titled “Gotchas”Cache-Control: no-store, privateis the safe default. Documents are tenant-scoped, and a shared proxy must never cache them.Content-Lengthis set for buffered responses only.PDFStreamuses chunked transfer encoding. Clients that need the size up front, such as range requests, should getPDF.- One response path per handler. Do not combine
polipagegin.PDFwithc.JSONin the same handler: both write headers and a body. Pick one andreturnright after. - The helpers set status
200unconditionally. Ac.Status(http.StatusCreated)beforepolipagegin.PDFis overwritten. For another status, write the headers yourself withc.Header(...)andc.Data(status, "application/pdf", bytes). - Keep PDF routes out of HTTP gzip. A PDF is already compressed. With
gin-contrib/gzip, exclude the route withgzip.WithExcludedExtensions([]string{".pdf"})orgzip.WithExcludedPaths(...). The same goes for reverse proxies that rewriteContent-Encoding.
Example
Section titled “Example”func invoicePDF(c *gin.Context) { client := polipagegin.ClientFrom(c) pdf, err := client.Render.PDF(c.Request.Context(), polipage.RenderInput{ Project: "billing", Template: "invoice", Data: map[string]any{"invoiceNumber": c.Param("number")}, }) if err != nil { polipagegin.WriteError(c, err) return } polipagegin.PDF(c, pdf, polipagegin.PDFOptions{Filename: "invoice-" + c.Param("number") + ".pdf"})}
func documentPreview(c *gin.Context) { result, err := polipagegin.ClientFrom(c).Documents.Preview(c.Request.Context(), c.Param("id")) if err != nil { polipagegin.WriteError(c, err) return } polipagegin.Preview(c, result.HTML)}
func documentPDF(c *gin.Context) { descriptor, err := polipagegin.ClientFrom(c).Documents.Get(c.Request.Context(), c.Param("id")) if err != nil { polipagegin.WriteError(c, err) return } polipagegin.DocumentRedirect(c, descriptor)}