Documentation
¶
Overview ¶
Package polipagegin integrates the Poli Page Go SDK with the Gin HTTP framework.
Install the middleware once on your router, then pull the per-request *polipage.Client off the gin context with ClientFrom. See README for the canonical example.
Index ¶
- Constants
- Variables
- func ClientFrom(c *gin.Context) *polipage.Client
- func DocumentRedirect(c *gin.Context, descriptor *polipage.DocumentDescriptor)
- func ErrorMiddleware() gin.HandlerFunc
- func Middleware(client *polipage.Client, opts ...MiddlewareOption) gin.HandlerFunc
- func PDF(c *gin.Context, body []byte, opts PDFOptions)
- func PDFStream(c *gin.Context, body io.Reader, opts PDFOptions)
- func Preview(c *gin.Context, html string)
- func WriteError(c *gin.Context, err error)
- type Config
- type MiddlewareOption
- type PDFOptions
Constants ¶
const ContextKey = "polipagegin.client"
ContextKey is the gin.Context key under which Middleware stores the per-request *polipage.Client. Exported so callers writing custom middleware (e.g. swapping the client per tenant) can read or write the same slot ClientFrom looks at.
Variables ¶
var ErrInvalidConfig = errors.New("polipagegin: invalid configuration")
ErrInvalidConfig is the sentinel every FromEnv error wraps. Use errors.Is(err, polipagegin.ErrInvalidConfig) for a single check when the specific failure does not matter to the caller.
Functions ¶
func ClientFrom ¶
ClientFrom returns the *polipage.Client stored on c by Middleware.
Panics with a clear message when the middleware was not installed on the route the request travelled through, or when something else has overwritten ContextKey with a value of the wrong type. Both cases are programmer errors, never expected at runtime — fail loud so the bug shows up in the first request a developer sends.
func DocumentRedirect ¶
func DocumentRedirect(c *gin.Context, descriptor *polipage.DocumentDescriptor)
DocumentRedirect issues a 302 redirect to descriptor.PresignedPDFURL, with Cache-Control "no-store, private". Useful when the caller wants the browser to fetch the PDF directly from object storage, saving a hop through the application.
The presigned URL has a ~15-minute TTL; once expired, re-fetch the descriptor via Documents.Get to refresh.
No body is written: a 302 + Location is sufficient, and a body would just add latency to a response the browser never displays.
func ErrorMiddleware ¶
func ErrorMiddleware() gin.HandlerFunc
ErrorMiddleware returns a gin.HandlerFunc that converts the last *polipage.Error queued on c.Errors into a JSON response:
{"code": "...", "message": "...", "status": 401, "requestId": "..."}
Middleware(client) already does this by default, so most applications never install ErrorMiddleware. Use it when the error handler was turned off with WithoutErrorHandler and should run somewhere else in the chain (for example on a different router group).
The middleware runs after the handler chain (it issues c.Next() and inspects c.Errors when control returns) and reads c.Errors.Last(), the convention downstream logging/tracing middleware typically follows. Multiple errors are tolerated; the most recent one drives the response.
Narrow on purpose: only *polipage.Error is intercepted. Any other error type is left untouched for the caller's existing error-handling middleware (or Gin's default) to deal with. Widening to all `error` values would swallow non-Poli-Page failures and destroy observability.
The body is the SDK's (*polipage.Error).ToPayload(), unchanged, with Content-Type: application/json. Its status is the API's HTTP status, 503 for network_error, 504 for timeout and 502 for invalid_response (a 2xx whose body is not JSON); the response uses the same status, or 500 when the payload has none, as for aborted or io_error (the body then carries "status": null). Nothing is written when the handler already wrote a response.
func Middleware ¶
func Middleware(client *polipage.Client, opts ...MiddlewareOption) gin.HandlerFunc
Middleware returns a gin.HandlerFunc that attaches client to every request's *gin.Context. Pull it back out inside a handler with ClientFrom.
The middleware also ships the error handler, on by default: when a handler queues a *polipage.Error with c.Error(err) and writes no response, the middleware answers with the error's ToPayload() body ({code, message, status, requestId}) and its status (500 when the payload has none). See ErrorMiddleware for the details. Pass WithoutErrorHandler() to turn it off.
Passing a nil client panics at install time — the failure mode we want is "the app fails to boot" not "the first request crashes deep inside the SDK". Reuse one *polipage.Client per process (it is safe for concurrent use); do not construct a fresh one per request.
The application must install gin.Recovery() so a ClientFrom panic from a missing middleware surfaces as HTTP 500 instead of crashing the process. gin.Default() includes Recovery; gin.New() does not.
func PDF ¶
func PDF(c *gin.Context, body []byte, opts PDFOptions)
PDF writes a buffered PDF response: status 200, Content-Type application/pdf, Content-Length, Cache-Control "no-store, private", X-Content-Type-Options nosniff, and an RFC 5987 Content-Disposition (attachment by default, inline when opts.Inline is true).
Status is set unconditionally to 200; a c.Status(...) call before PDF is silently overwritten. Callers that need a non-200 PDF response (rare) must roll the headers and body by hand.
func PDFStream ¶
func PDFStream(c *gin.Context, body io.Reader, opts PDFOptions)
PDFStream writes a chunked PDF response — same headers as PDF plus X-Accel-Buffering: no (opt nginx-style proxies out of response buffering), and no Content-Length (the response is chunked). Reads from body 32 KB at a time, writing and flushing each chunk so bytes leave the process as they arrive rather than sitting in a buffer until EOF.
PDFStream does NOT call body.Close() — the handler that obtained the io.ReadCloser from client.Render.PDFStream is responsible for `defer body.Close()` before invoking this helper. The signature takes io.Reader (not io.ReadCloser) to advertise that policy.
Passing a nil reader panics: a nil reader is a programmer error, not a runtime fallback. The panic message says so; the gin.Recovery prerequisite (see Middleware) turns it into a 500 in the unlikely event it slips into production.
PDFStream reads the first chunk before it writes any header. When that first read fails, nothing is set or written: the error is queued on c.Errors and the middleware's error handler (or the caller) answers it as a normal error instead of a PDF response.
Later failures (Read returns a non-EOF error, or Write fails) are attached to c.Errors via c.Error(...), and the response is cut short so the client sees a truncated transfer instead of a complete-looking PDF: the status and the bytes already sent cannot be taken back, so PDFStream sets a write deadline in the past on the server's own writer, which it reaches through writer wrappers (see abortResponse): net/http closes the HTTP/1.x connection without ending the chunked body, or resets the HTTP/2 stream, also behind gin.Recovery(). When the deadline cannot be set (a ResponseRecorder, or a wrapper that hides its writer), it panics with http.ErrAbortHandler, net/http's signal to abort the response. gin.Recovery() catches that panic, so behind Recovery such a response ends as a short 200: gin v1.9 to v1.11 Recovery logs it with a stack trace; v1.12 logs the error and a request dump without a stack, and adds "net/http: abort Handler" to c.Errors. On the panic path, middleware code after c.Next() (gin.Logger, for one) does not run unless a recovery middleware sits inside it.
func Preview ¶
Preview writes an HTML preview response: status 200, Content-Type "text/html; charset=utf-8", Cache-Control "no-store, private", X-Content-Type-Options nosniff. No Content-Disposition — the response is meant to be rendered, not downloaded.
func WriteError ¶
WriteError is the per-handler version of the error handler: same JSON body and status. Use it inside a handler when the error flow is explicit rather than queued on c.Errors.
A non-*polipage.Error argument produces an HTTP 500 with no body: the caller's logging middleware owns the message. WriteError is intentionally opaque to anything it does not recognise; widening it would re-introduce the destroyed-observability hazard the error handler already avoids.
Types ¶
type Config ¶
type Config struct {
// APIKey is the Poli Page API key, pp_{environment}_{32 lowercase hex}
// (pp_sandbox_…, pp_live_…, pp_staging_…). Required and non-empty; any
// other value is accepted, because the API is the only authority on keys.
APIKey string
// BaseURL overrides the API base URL.
// Must be an http:// or https:// URL when set.
BaseURL string
// Timeout is the per-request deadline applied when the caller's
// context has no deadline of its own. Must be in (0, 10m] when set.
Timeout time.Duration
// MaxRetries is the retry budget on top of the initial attempt.
// Must be in [0, 10] when set. Explicit 0 (no retries) is honored
// — see Options().
MaxRetries int
// RetryDelay is the base exponential-backoff delay. Must be in
// [0, 30s] when set. Explicit 0 (immediate retry, no backoff) is
// honored — see Options().
RetryDelay time.Duration
// contains filtered or unexported fields
}
Config is the env-driven configuration for the Poli Page SDK client. Populate via FromEnv (which parses POLI_PAGE_* environment variables and validates them) and pass Options() to polipage.NewClient.
Fields left at their zero value after FromEnv mean "the env var was not set, fall back to the SDK default"; Options omits the corresponding option.With* call so polipage.NewClient applies its own defaults (DefaultMaxRetries, DefaultRetryDelay, etc.).
func FromEnv ¶
FromEnv loads Config from the documented POLI_PAGE_* environment variables, validating each one. On failure the returned error wraps ErrInvalidConfig and names the offending variable in its message.
FromEnv uses os.LookupEnv (not os.Getenv) so an empty value can be distinguished from an unset variable for the optional fields; either state is treated as "fall back to the SDK default".
func (Config) Options ¶
func (c Config) Options() []option.RequestOption
Options returns the option.RequestOption slice to pass to polipage.NewClient. Only fields the user explicitly set (via env) contribute — fields left at their zero value defer to the SDK's own defaults. Explicit zero for MaxRetries / RetryDelay is honored thanks to the internal set-tracking.
type MiddlewareOption ¶
type MiddlewareOption func(*middlewareConfig)
MiddlewareOption configures Middleware.
func WithoutErrorHandler ¶
func WithoutErrorHandler() MiddlewareOption
WithoutErrorHandler turns off the error handler Middleware installs by default: a *polipage.Error queued on c.Errors is then left for the application's own error handling (or ErrorMiddleware, installed elsewhere).
type PDFOptions ¶
type PDFOptions struct {
// Filename is the filename presented to the User-Agent. Empty defaults
// to "document.pdf".
Filename string
// Inline picks "inline" instead of "attachment". Useful when the
// response is rendered inside an <iframe> (the demo dashboard does
// this) so the browser views the PDF in place instead of triggering
// a download.
Inline bool
}
PDFOptions controls Content-Disposition for the PDF and PDFStream helpers.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
polipage-render
command
Package main implements the polipage-render CLI binary.
|
Package main implements the polipage-render CLI binary. |